Evaluating performance of CSS Variable vs style value as a React props

Recently I am building a slider that involves animating images on mouse hover. Since the animation occured on all images which are children component, the value of the image hover needs to be passed dow through a React props. During the process, I was conflicted between using CSS variable to update the image background position, or just passing it through the props. I did a little benchmarking to see what really happens.

Benchmark object

The use case of this benchmark is animating images on mouse hover. You can see the demo here. I wrote tutorial on the slider on this post. The component looks like this:


// Solution with React props

const SlideItem = ({images}) => {
  const [hoverOffset, setHoverOffset] = useState<string>('50%');
  const handleSlideHover = (e: React.MouseEvent) => {
    const innerWidth = window.innerWidth;
    const calcOffset = (e.clientX / innerWidth) * 100;
    setHoverOffset(`${calcOffset}%`);
  };
  return (
    <ul onMouseMove={(e) => handleSlideHover(e)}>
      {images.map(({url}) => (
        <li>
          <SlideImage src={url} offset={hoverOffset}>
        </li>
      ))}
    </ul>
  )
}
const SlideImage = ({src, offset}) => {
  const backgroundPositionX = offset;
  const backgroundImage = `url(${src})`;
  const styleInline = {backgroundPositionX, backgroundImage};
  return (
    <div
      style={styleInline}
    />
  )
}

// Solution with CSS Variable

const SlideItem = ({images}) => {
  const [hoverOffset, setHoverOffset] = useState<any>({});
  const handleSlideHover = (e: React.MouseEvent) => {
    const innerWidth = window.innerWidth;
    const calcOffset = (e.clientX / innerWidth) * 100;
    setHoverOffset({`--offsetX: ${calcOffset}%`});
  };
  return (
    <ul onMouseMove={(e) => handleSlideHover(e)} style={hoverOffset}>
      {images.map(({url}) => (
        <li>
          <SlideImage src={url}>
        </li>
      ))}
    </ul>
  )
}
const SlideImage = ({src}) => {
  const backgroundImage = `url(${src})`;
  const styleInline = {backgroundImage};
  return (
    <div
      className='slideImage'
      style={styleInline}
    />
  )
}

// CSS
.slideImage {
  background-position-x: var(--offsetX);
}

The main difference is:

  1. React props solution

    Re-calculating CSS value occured on children level only

  2. CSS variable solution

    Re-calculating CSS value occured on both parent level and children level

Both is re-rendering on the child level only.

Method

To compare the performance, I used Chrome DevTools performance tab. I put the CPU throttled to 6x slowdown.

image

The next step is, I hit the record button and then moving the mouse over the slider for 5 seconds. I then captured the results and analyze it down below.

Benchmark

General performance of using CSS variable.

image

General performance of using inline style via updating React props.

image

As you can see from the screenshot above, when use inline style via passing a React props value, it shows a faster performance in most of the aspects except painting. When we zoom in deeper to a similar segment, recalculation took longer in the solution that is using CSS variable.

Let's zoom out and see the general processing graph from both solution.

CSS variable solution

image

React props solution

image

From the graph above, we can see that the spikes are not that much for the solution that is using React props and inline styling. This give us a second prove that the React props solution perform better compared to CSS variable.

For the last method, I used FPS meter tool which we can enable in Chrome by pressing Cmd + Shift + P in Mac or Ctr + Shift + P in windows. And then we can type "FPS meter". This will help us monitor the number of frame per second. The target is to hit 60 fps. If we can run our animation on 60 fps, it means it will look smooth.

CSS variable solution

image

React props solution

image

From the graph above, we can see that both hits the same FPS on 58.8. Remember that we throttled the GPU to 6x slower on the browser. So it is not a big issue. It is alsow shown that both are using the same GPU memory usage which is 33.0 MB.

Summary

The solution that is using inline style and passing a value via React props, performed better compared to CSS variable solution. Even though, the differences are on milisecond. My opinion is that probably it does not matter too much on a small project. But if we have a lot of images and slides on the slider, we should consider not using CSS variables for assigning a dynamic value to a CSS property. Especially on animation triggered by dynamic event such as mouse movement.

More readings: