React, SMIL + viewBox animation notes

Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
No comments yet. Be the first to comment.
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

viewBox = your camera lens
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="20" />
</svg>
The circle is always at coordinate (50,50) with radius 20. The circle never moves. But when you animate viewBox, you're changing what part of the coordinate space you can see.
Think of it like a camera filming a stage:
Stage (infinite SVG canvas):
┌─────────────────────────────────┐
│ 🎭 🎪 🎨 │
│ │
│ 🎵 ⭐ (50,50) 🎯 │
│ │
│ 🎸 🎺 🎹 │
└─────────────────────────────────┘
viewBox="0 0 100 100" = Camera shows top-left corner, 100×100 area:
Camera view:
┌─────────────┐
│ 🎭 🎪 │
│ │
│ 🎵 ⭐ │ ← Star is visible at center
│ │
└─────────────┘
viewBox="50 50 100 100" = Camera moves to show different area:
Camera view:
┌─────────────┐
│ ⭐ 🎯 │ ← Same star, now at top-left!
│ │
│ 🎺 🎹 │
│ │
└─────────────┘
The star never moved -> we just changed our view of it!
Here's the tricky part with React:
What you want to happen:
viewBox starts at "0 0 100 100"
User changes prop to "50 50 100 100"
viewBox smoothly animates from old → new
What actually happens:
React re-renders with new prop
SVG immediately gets viewBox="50 50 100 100"
animate element also gets to="50 50 100 100"
You're trying to animate from "50 50 100 100" to "50 50 100 100"
No animation!
function Viewbox({ viewbox, children }) {
const previousViewbox = usePrevious(viewbox); // Remember old value
const animateRef = useRef();
useEffect(() => {
if (previousViewbox) {
// Skip first render
animateRef.current?.beginElement(); // Start animation
}
}, [viewbox]);
return (
<svg viewBox={viewbox}>
{" "}
{/* React sets this immediately */}
{children}
<animate
ref={animateRef}
attributeName="viewBox"
from={previousViewbox} // Old value
to={viewbox} // New value
dur="0.3s"
begin="indefinite" // Don't auto-start
/>
</svg>
);
}
Initial state:
viewbox prop = "0 0 100 100"
previousViewbox = undefined
SVG shows: viewBox="0 0 100 100"
User changes to "50 50 100 100":
React re-render:
viewbox prop = "50 50 100 100"
previousViewbox = "0 0 100 100" (from usePrevious)
SVG immediately shows: viewBox="50 50 100 100" (React controlled)
animate element gets:
<animate from="0 0 100 100" to="50 50 100 100" />
useEffect runs:
animateRef.current.beginElement(); // Start the animation
SMIL animation takes over:
Overrides the SVG's viewBox attribute
Smoothly interpolates: "0 0 100 100" → "50 50 100 100"
Takes 0.3 seconds
Animation ends:
SMIL stops controlling viewBox
SVG returns to React's value: "50 50 100 100"
Seamless handoff!
function usePrevious(value) {
const ref = useRef(value);
useEffect(() => {
ref.current = value; // Update AFTER render
}, [value]);
return ref.current; // Return OLD value during render
}
Timeline:
Render 1: value="A" → usePrevious returns "A"
Render 2: value="B" → usePrevious returns "A" (useEffect hasn't run yet)
useEffect runs → ref.current becomes "B"
Render 3: value="C" → usePrevious returns "B"
useEffect runs → ref.current becomes "C"
Zoom in:
// From wide view to focused view
viewBox="0 0 200 200" → viewBox="75 75 50 50"
Pan across:
// Slide the camera right
viewBox="0 0 100 100" → viewBox="50 0 100 100"
Zoom out:
// From detail to overview
viewBox="40 40 20 20" → viewBox="0 0 100 100"
Follow an object:
// Keep character centered as they move
character at (30,40) → viewBox="5 15 50 50"
character at (70,20) → viewBox="45 -5 50 50"
Performance: No DOM elements move -> just the viewport
Simplicity: One attribute controls complex camera movements
Flexibility: Works with any SVG content
Smoothness: Native browser interpolation