Exploring Infinite Canvas: My Journey & Insights

I’ve always been fascinated by infinite canvas interfaces - those smooth, zoomable, pannable spaces that apps like Figma and Miro use. So I decided to reverse-engineer one. Turns out, you only need 2 HTML elements to make it work.

The Core Concept

At its heart, an infinite canvas is surprisingly simple:

  1. A viewport (the window you look through)
  2. A canvas (the surface that moves)

That’s it. Everything else is just math and event handling.

The Implementation

Here’s the minimal setup that got me started:

<main id="infinite-canvas" style={{ width: "100dvw", height: "100dvh" }}>
  <div 
    id="object-to-move" 
    style={{
      position: "absolute",
      transform: `scale(${scale}) translate(${x}px, ${y}px)`
    }}
  >
    Scroll to move or pinch to zoom
  </div>
</main>

The magic happens in that transform property. By combining scale() and translate(), you get smooth zooming and panning with hardware acceleration.

Handling Gestures

The tricky part was making it feel natural. I experimented with a few approaches:

Approach 1: Raw Event Listeners

Started with basic wheel and pointermove events. Works, but the math for pinch-to-zoom gets complex quickly.

Approach 2: @use-gesture/react

This library from the Poimandres collective is fantastic. It handles all the edge cases:

import { useGesture } from '@use-gesture/react'

const bind = useGesture({
  onWheel: ({ delta: [dx, dy] }) => {
    setPosition([x - dx, y - dy])
  },
  onPinch: ({ offset: [scale] }) => {
    setScale(scale)
  }
})

The Gotchas I Hit

  1. Transform Origin: Always transform from the cursor position, not the center. Users expect to zoom into what they’re looking at.

  2. Bounds Calculation: When you’re zoomed in, the coordinate system gets weird. You need to account for scale when calculating boundaries.

  3. Performance: DOM updates are expensive. Use transform instead of changing top/left. Consider using will-change: transform for smoother animations.

What I Learned

The biggest insight? Most “complex” UI patterns are simpler than they appear. The trick is understanding the core mechanics, then layering on the polish.

I spent a week thinking I needed a complex state machine, collision detection, and a rendering pipeline. Turns out I just needed CSS transforms and some event math.

Next Steps

Now that I have the basics working, I want to add:

  • Momentum scrolling for that iOS-like feel
  • Minimap navigation
  • Snap-to-grid functionality
  • Multi-touch gesture support

The code is surprisingly portable too. Once you understand the transform math, you can implement this in any framework - React, Vue, vanilla JS, whatever.

Sometimes the best way to learn is to just open DevTools on your favorite app and see how they did it. You’d be surprised how much you can figure out just by inspecting the DOM and watching the styles change.