A lesson about React, D3, and animation performance

Here’s a lesson about JavaScript performance you already know, but always forget. At least I do 😇

You can totally use JavaScript to build performant 60 fps animation. Even with thousands of elements all moving on every frame of your animation.

NOTE: This is a cross-post from my newsletter. I publish each email two weeks after it’s sent. Subscribe to get more content like this earlier right in your inbox! 💌

A few years ago (wow time flies), I used React, Redux, and canvas to push a particle generator past 20,000 elements. Even on a mobile phone.

The basic idea goes like this:

  • put whatever you’re animating in state
  • change state 60 times per second
  • let React re-render
  • animation!

Gives you a lot of control. Sometimes, too much. For an easier approach, you can use D3 transitions. Especially useful if you’re already building a dataviz or something.

About 10 lines of code and you have Declarative D3 transitions with react 👇

Works great 👌

DOM recursion is tough on React’s engine

But then you do something stupid. Like visualize a tree-based data structure using recursion.

Here’s what using D3 transitions to animate just 536 elements looks like when those elements are deeply nested.

Yes, that’s with a production build of React. Doesn’t help much.

You can read more about how that flamegraph works in this Tiny React & D3 flamegraph tutorial. Point is it’s declarative, easy to use, and has D3 transitions for the animation.

But the performance is terrible ☹️

The problem is that propagating information through a deeply nested tree is just about the worst case scenario for React’s engine. It means a huge part of the tree gets flagged for updates and there’s no chance to optimize.

There’s two ways you can solve this:

  1. Refactor your code so it uses a flat DOM/React structure
  2. Use CSS transitions instead

The first option is great. A modified version of the tail recursion optimisation problem popular in computer science classrooms.

Tail recursion is when you call your function as the last operation of your function. Recursion happens last.

const Flamegraph = (...) => ( <g> <rect> <Flamegraph> </g> )

You can provably always unwind these into loops. Loops have better performance.

In many other languages, the compiler performs this optimization for you. You write a tail recursion, and the computer executes a loop.

I wonder if React could do this 🧐 I’ll have to ask. It’s probably not something enough people would care about.

It’s cumbersome to do manually.

CSS animation to the rescue

Kalle Ott helpfully solved the problem with CSS animations. Because Kalle Ott is awesome.

Using CSS transitions works great 👇

Browsers are crazy good at animating things with CSS. Part of the browser’s rendering engine, runs on the GPU if at all possible, works great.

You’re doing transitions already anyway, not like you need the control of JavaScript. 🤷‍♀️

Plus it’s pretty easy to do.

Define some transitions

const transitionTime = "250ms"; const transitionCurve = "cubic-bezier(0.85, 0.69, 0.71, 1.32)"; const widthTransition = `width ${transitionTime} ${transitionCurve}`; const transformTransition = `transform ${transitionTime} ${transitionCurve}`;

Apply them on the right elements using JavaScript

componentDidUpdate() { const { width, x, y } = this.props; d3select(this.gRef.current).attr("transform", `translate(${x}, ${y})`); this.hideLabel(); } // render <g transform={`translate(${x}, ${y})`} style= onClick={this.onClick} ref={this.gRef} >

Same API as before to the outside: Change x and width when you need to update. Except now the x transition is handled by transformTransition and the width transition is triggered in componentDidUpdate with way fewer lines than before.

Lesson learned. Thanks Kalle 👌

In other news

Choosing an intern is going well. We had a webinar last week where I explained the new React context API and answered a bunch of questions. It was fun.Here’s a recap.

Coming up with that and making some big decisions ate up most of my week. More on those in a blogpost next week :)

Learn While You Poop is coming back on air soon, I promise. I know I said it a few times already, but the content is now planned out and the buffer is building. 🤞

A few cool things

Here’s a few cool things from the week:

Cheers,

~Swizec

PS: Would you be interested in a paid React & D3 webinar? Similar to my 7 hour workshops, but shorter and way, way cheaper. I’m developing some new material for a book/course update and wanna test it out. Leave me a comment!


P.S. If you like this, make sure to subscribe, follow me on Twitter, buy me lunch, and share this with your friends. 😄