CSS text-shadow is, somehow, still laggy (sometimes) - use drop-shadow instead (maybe).

I was looking for a lightweight syntax highlighter, and I started reading up on PrismJS. To my surprise, there was a massive framerate drop when I scrolled to the very bottom of the page:

On the left: the laggy text-shadow-based version of the PrismJS webpage.
On the right: a drop-shadow-based version of the same page.

After some investigating, it turned out that the footer section, with the CSS property text-shadow, was to blame. But this lag only happened in Chrome and Edge - both based on the Blink rendering engine - and not in Safari or Firefox. This was a bit of a surprise to me since text-shadow is an ancient CSS3 property - Chrome should really have optimized this by now.

The fix: drop-shadow()

drop-shadow() can be a direct replacement, with some caveats.

.shadow {
  /* drop-shadow(offset-x offset-y blur-radius color) */
  filter: drop-shadow(4px 8px 12px black);

  /* Multiple filters can be combined in one rule: */
  filter: drop-shadow(4px 8px 12px black) drop-shadow(2px 16px 4px pink);
}

Depending on your use case, you may need to selectively apply drop-shadow to individual content nodes. The drop-shadow() effect is based on the entire element's alpha mask, so it'll work more like a box-shadow if the element has an opaque background.

<div style="background: pink">
  <p class="shadow">Content here</p>
  <ul style="background: white">
    <li class="shadow">Item 1</li>
    <li class="shadow">Item 2</li>
    <li class="shadow">Item 3</li>
  </ul>
</div>

Why does this work?

The filter attribute in CSS is used for applying SVG Filters, in addition to just simple primitives like blurs and shadows. That's probably why it's more performant than text-shadow, which probably was never rewritten to make use of the SVG-focused optimizations.

The old, no-longer-working fix

Apparently, this lag has been an issue dating back to its introduction in CSS3. This old Stack Overflow post from 2011 suggests that triggering hardware acceleration could work in WebKit:

.shadow {
  text-shadow: 0 0 black;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

But I haven't been able to get this to resolve the PrismJS homepage lag with Chrome (now based on Blink) in 2023.

Edit: Actually, I was half-right...

I took another look to see why that particular text-shadow on PrismJS.com was so laggy. I realized that the lag was due to the combination of two factors: the text-shadow was being rendered on top of the fixed-positioned background image. In this case, removing the unnecessary fixed keyword also eliminated the lag.

But this whole drop-shadow idea could still be helpful if you run into this kind of issue in a similar context, where you do need to preserve some otherwise expensive graphics. We'll see if Chrome patches this lag in a future update.