React for Data Visualization
Student Login

Use transitions for simple animation

Game loops are great for fine-grained control. And when all you need is a little flourish on user action, that's where transitions shine.

No details, just keyframes.

Transitions let you animate SVG elements by saying "I want this property to change to this new value and take this long to do so".

Start-end keyframes are the simplest. You define the starting position. Start a transition. Define the end position. D3 figures out the rest. Everything from calculating the perfect rate of change to match your start and end values and your duration, to what to change and handling dropped frames.

Quite magical.

You can also sequence transitions to create complex keyframe-based animation. Each new transition definition is like a new keyframe. D3 figures out the rest.

Better yet, you can use easing functions to make your animation look more natural. Make rate of change follow a mathematical curve to create smooth natural movement.

You can read more about the why of easing functions in Disney's 12 Basic Principles of Animation.

Bottom line is that it makes your animation feel natural.

How they work is hard to explain. You can grok part of it in my Custom transition tweens article.

But don't worry about it. All you have to know is that many easing functions exist. easings.net lists the common ones. D3 implements everything on that list.

Let's try an example: A swipe transition.

Build a swipe transition

You're going to build a ball that swipes left and right, up and down. The goal is to build a component that handles transitions internally and behaves like any other React component to the outside.

I've prepared an example CodeSandbox for you here:

How it works

The key to this transition effect is a 3 step process:

  1. Copy props into state - your staging area
  2. Run a D3 transition as a side-effect when props change
  3. Update state so React knows what's up
const Ball = ({ x }) => {
const [realX, setRealX] = useState(x)
const circleRef = useRef()
useEffect(() => {
let el = d3.select(circleRef.current)
el.transition()
.duration(800)
.ease(d3.easeBounceOut)
.attr("cx", x)
.on("end", () => setRealX(x))
}, [x])
return <circle r="10" cx={realX} cy={10} ref={circleRef} />
}

We save the incoming prop into state by default. This triggers a render. You're using state to render the component so any future prop changes won't render.

This is important. It lets you run transitions.

When the x prop changes, a useEffect runs and starts a D3 transition. We're using the blackbox rendering approach for this part: D3 selects the DOM node and takes over.

A D3 transition then handles animation, manipulates DOM directly, and takes care of timing, easing functions, the whole shebang.

When our transitions ends, we update React state so React knows what's up. Component re-renders again.

Exercise: Add a vertical transition

Now that you know the theory, fork the codesandbox and add a vertical transition. Try to run the vertical transition in parallel with a different easing function.

Look at my solution only after you've given it a shot :)

Solution

You can transition any attribute you'd like, you can run transitions sequentally, and you can run them in parallel by giving them names.

D3 handles all interruptes and tricky math for you ✌️

Next we're going to look at combining transitions and game loops for some truly powerful stuff.

Previous:
Game loop animation with a bouncy ball (7:27)
Next:
Powerful animation with transitions and game loops combined (24:40)
Created by Swizec with ❤️