Step 3 - canvas
2 min czytania
Build the canvas rendering shell in the /board/:boardId component. Rendering is a single HTML with a 2D context - not SVG, not DOM elements.
Coordinate system:
all elements live in shared world units. Viewport = {x, y, scale}. screenToWorld(v, sx, sy) = ((sx - v.x) / v.scale, (sy - v.y) / v.scale). Render pass: ctx.setTransform(dpr,0,0,dpr,0,0), then translate(v.x, v.y) and scale(v.scale, v.scale) for world content; restore transform for screen-space overlays (remote cursors etc., later).
Render loop:
a scheduleRender() that sets a dirty flag and requests ONE animationFrame. Create a single effect() in the constructor that reads every rendered signal (elements, viewport, background...) and calls scheduleRender - this coalesces any number of state changes into one repaint. Size the canvas backing store by devicePixelRatio; re-render via a ResizeObserver on the canvas element.
Pan/zoom:
- Attach the wheel listener to the whole board shell in CAPTURE phase with passive:false, so a trackpad pinch over any floating panel still zooms the board and never the page.
- ctrlKey || metaKey => zoom toward the pointer with factor = Math.exp(-deltaY * 0.01), scale clamped to [0.2, 5]. Plain wheel => pan by deltaX/deltaY.
- Touch: track active pointers in a Map; when a second pointer lands, abort any in-progress single-pointer action and switch to two-finger pan via centroid tracking.
- Zoom in/out buttons and a reset-view button.
- Persist the viewport per board in localStorage (debounced) and restore it on entry.
Background: board-level background color plus a grid mode 'dots' | 'lines' | 'plain', painted in world space before elements. Support a dark canvas color that flips grid strokes to light.
Acceptance: an empty board pans with drag/wheel/touch, zooms toward the cursor with ctrl+wheel within 0.2-5x, grid scales with zoom, view survives a reload, and page never scrolls or zooms while interacting over the canvas.
Dyskusja
Komentarze: 0Brak komentarzy. Rozpocznij dyskusję.