Skip to main content
Arthur Ha

Performance

Why Your Animations Stutter - Batch Your DOM Style Changes

February 18, 2026

Your dropdown animates smoothly on your machine. On your teammate's laptop, it stutters. On mobile, it feels sluggish.

Often the culprit isn't the animation itself - it's how you're applying styles. Every individual style change can force the browser to recalculate layout (reflow) and repaint. Do it four times, and you get four reflows. Do it once, and you get one.

The problem: one property, one reflow

TypeScript
function updateElementStyles(element: HTMLElement) {
  element.style.width = "100px"; // reflow
  element.style.height = "200px"; // reflow
  element.style.backgroundColor = "blue"; // reflow
  element.style.border = "1px solid black"; // reflow
}

Each assignment can trigger a layout pass. The browser doesn't know you're about to change more - it recalculates after every change. That adds up fast.

The fix: batch your changes

Option 1: Use a class (best)

TypeScript
// CSS
.highlighted-box {
  width: 100px;
  height: 200px;
  background-color: blue;
  border: 1px solid black;
}

// JavaScript
function updateElementStyles(element: HTMLElement) {
  element.classList.add('highlighted-box')
}

One class toggle. One reflow. Classes are cached by the browser and keep your logic separate from your styles.

Option 2: Use cssText

TypeScript
function updateElementStyles(element: HTMLElement) {
  element.style.cssText = `
    width: 100px;
    height: 200px;
    background-color: blue;
    border: 1px solid black;
  `;
}

Same idea - apply all changes in one shot instead of four.

In React: avoid the ref + useEffect pattern

TSX
// Incorrect: mutating styles one by one in useEffect
function Box({ isHighlighted }: { isHighlighted: boolean }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (ref.current && isHighlighted) {
      ref.current.style.width = "100px";
      ref.current.style.height = "200px";
      ref.current.style.backgroundColor = "blue";
    }
  }, [isHighlighted]);

  return <div ref={ref}>Content</div>;
}

This pattern is easy to reach for when you "need" imperative DOM access. You usually don't.

TSX
// Correct: let React handle it with a class
function Box({ isHighlighted }: { isHighlighted: boolean }) {
  return <div className={isHighlighted ? "highlighted-box" : ""}>Content</div>;
}

React batches updates. CSS classes batch styles. Together they keep reflows to a minimum.

Rule of thumb: Prefer classes over inline styles. Batch your DOM changes. Your users' devices will thank you.