// `viewBox` on a is the fixed-aspect answer to "many screen sizes": you // author the whole scene once, in your own made-up design units, and the view // scales that space to fit its box. It is SVG's viewBox generalized to any // subtree, not an SVG-only thing. // // Four facts, each demonstrated below: // 1. It NEVER sizes the element. `viewBox` is a pure fit transform; the box // still takes its size from layout (here `flex={1}`). A view with only a // viewBox and no layout size is zero-sized and paints nothing. // 2. It fits UNIFORMLY and centers - one scale for both axes, SVG's default // preserveAspectRatio. Content never stretches; the leftover on the loose // axis is letterbox, showing the window background through it. // 3. Children live in DESIGN space. Their x/y, w/h, fontSize, stroke widths - // everything resolves against the design size, not the box. The box a child // inherits IS the design size, so a bare `d-rect` fills the design space // and detached text wraps at its width. // 4. Pointer coordinates arrive in design space too. localX/localY on the // viewBox view (and on anything under it) read in design units, so no // scale factor is threaded through the app's hit math. // // The payoff: no `windowSizeClass` branching, no per-breakpoint sizes, no // scale factor anywhere. The same code runs unchanged from a desktop window to // a phone. Reach for reflow (responsive-grid.tsx) only when the layout // genuinely rearranges across form factors; for content with fixed internal // geometry - diagrams, slides, dashboards, game boards, emulator screens - fit // one design space instead. // // Resize the window and watch: the scene keeps its aspect ratio, everything // including text scales together, and the readout reports the same design // coordinates for the same spot on the scene at any window size. import { render, createSignal, Show } from "@solidrt/core" // The design space. Every number below is in these units - invent whatever // suits the content and stay in it. 640x400 is 16:10. const DESIGN_W = 640 const DESIGN_H = 400 function App() { let [at, setAt] = createSignal<{ x: number; y: number } | null>(null) let round = (v: number) => Math.round(v) return ( // The window background is what shows through the letterbox bars. {/* flex={1} sizes the box; viewBox only maps content into it (fact 1). */} setAt({ x: e.localX, y: e.localY })} onPointerLeave={() => setAt(null)} > {/* No w/h, so it fills the box it inherits - which under a viewBox is the design space (fact 3). Its edges are the letterbox edges. */} {/* A scene at literal design coordinates. No breakpoints, no windowSize() reads, no scale factor: authored once, at this size. */} fixed design space {DESIGN_W} x {DESIGN_H} {/* Pointer position in design units (fact 4), drawn in design units. Non-keyed Show keeps the marker node mounted across moves (a ternary on at() would recreate it every event); the accessor reads update its props fine-grained. */} {at() ? `design x ${round(at()!.x)}, y ${round(at()!.y)}` : "move the pointer over the scene"} {(a) => } ) } render(() => )