Bubble Board — a Draggable Grid That Never Re-renders

How the BubbleBoard keeps 120 bubbles in sync with a drag gesture using Framer Motion Motion Values instead of React state.

CreativeFramer MotionPerformance

The BubbleBoard is a showpiece: a 10 x 12 grid of pastel bubbles you can grab and drag around inside a window. The interesting engineering is that none of this needs React state. A single drag gesture updates a plane, and 120 SVG-style nodes follow it at 60fps.

Motion Values, not re-renders

If you stored the drag offset in useState, every frame of the gesture would re-render the whole grid. Instead, the board keeps two useMotionValues for the draggable plane:

const x = useMotionValue(initialOffset);
const y = useMotionValue(initialOffset);

Framer Motion updates these values outside React's render cycle, directly in the animation loop.

Projecting plane space to screen space

Each bubble is absolutely positioned inside the plane. Its screen position is the plane offset plus its own grid offset:

const screen = planeX.get() + xOffset + 20;
x.set(mapScreenToXOffset(screen));

mapScreenToXOffset is a transform() that remaps a screen-space range onto a small pixel offset, so bubbles near the drag edge slightly compress toward the center — giving the whole grid a subtle parallax feel.

The drag gesture

The plane itself is the drag surface:

<motion.div
  drag
  dragConstraints={{ left: -650, right: 50, top: -600, bottom: 50 }}
  ...
/>

Constraints clamp the travel so the grid stays inside the window, and each child reads the updated Motion Values through its own effect subscription.

Key takeaways

  • Use Motion Values for gestures that update many nodes each frame.
  • A single onChange subscription per axis beats prop-drilling state into every child.
  • Constrain the plane once; children compute their offset declaratively.