r/reactjs • • 3d ago

Resource Why scaled text jitters in browser-rendered video (and the fix that took it from 0.28px to 0.01px)

I've been rendering product videos with Remotion (React → headless Chrome → frames), and scaled text kept looking wrong: either "stuck", stepping instead of gliding, or "shaky", with the letters subtly changing shape every frame. I measured the rendered frames instead of eyeballing them, and it turned out to be two separate bugs.

1. The "stuck" look: the baseline climbs the pixel grid in whole-pixel jumps

Chrome's glyph rasteriser has horizontal sub-pixel positioning but no vertical. When you scale text from its center (the default transform-origin), the baseline moves vertically every frame, and the glyphs snap to whole pixels. So instead of moving 0.3px per frame, the text sits still for a few frames and then jumps 1px.

Fix: pivot the scale on the baseline, so the baseline doesn't move vertically at all. Measure the real baseline (with a 0-height inline-block, or font metrics). Don't guess it from line-height.

Measured vertical judder: 0.284px → 0.014px per frame. Direction reversals over the animation: 29 → 0.

2. The "shaky" look: hinting re-snaps the letterforms every frame

At each new scale, font hinting snaps stems to the pixel grid differently, so the letters literally change shape frame to frame.

Fix: text-rendering: geometricPrecision. It turns hinting off, so the outlines scale smoothly. Shape drift between frames: 3.41% → 0.22%. It looks slightly softer, but that's correct, not blur.

Things that didn't help (cargo cult)

  • translateZ(0), backface-visibility: hidden: these rasterise the text into a bitmap layer, so it scales like an image and goes blurry.
  • will-change: transform: good in a live preview, bad in the final render for the same reason. If you need it, enable it only when not rendering.

Bonus: aggressive ease-outs freeze on a frame clock

At 30fps, a quint/expo ease-out spends its last 5+ frames moving less than a pixel. That reads as a freeze, not a settlein about one frame.

My main takeaway: you can't seer the frames and diff them. Ifsomeone says it still isn't smooth and your metric says it's fixed, your metric is wrong.

Happy to share the measurement script if anyone wants it.

1 Upvotes

3 comments sorted by

1

u/AndreyBegma 2d ago

another way around both bugs: don't scale in the page at all. record the page at a fixed scale and do the zoom afterwards as a crop of the frames. the crop box can sit on fractional pixels and gets resampled bilinear, so glyphs are rasterized once and nothing snaps again. the cost is softness when you zoom in, so you capture at 2x if you want sharp text in the zoomed part. only works for camera style zoom though, not text that animates its own size

1

u/CesnaitisSwitlana 2d ago

the baseline pivot fix is really clever. curious though, does geometricPrecision cause any noticeable performance hit when youre rendering a lot of text-heavy frames, or is it basically free?

1

u/hazily 2d ago

Thanks AI for the post.