CSS - Transforms

CSS deep-dive: matrix3d() for a frame-perfect custom scrollbar

I haven’t checked how accessible this is, and I’m torn on the concept of custom scrollbars, but this is interesting at the very least:

Custom scrollbars are extremely rare and that’s mostly due to the fact that scrollbars are one of the remaining bits on the web that are pretty much unstylable (I’m looking at you, date picker). You can use JavaScript to build your own, but that’s expensive, low fidelity and can feel laggy. In this article we will leverage some unconventional CSS matrices to build a custom scroller that doesn’t require any JavaScript while scrolling, just some setup code.


You don’t care about the nitty gritty? You just want to look at the Nyan cat demo and get the library? You can find the demo’s code in our GitHub repo.

GPU Animation: Doing It Right

Most people now know that modern web browsers use the GPU to render parts of web pages, especially ones with animation. For example, a CSS animation using the transform property looks much smoother than one using the left and top properties. But if you ask, “How do I get smooth animation from the GPU?” in most cases, you’ll hear something like, “Use transform: translateZ(0) or will-change: transform.”

These properties have become something like how we used zoom: 1 for Internet Explorer 6 (if you catch my drift) in terms of preparing animation for the GPU — or compositing, as browser vendors like to call it.

But sometimes animation that is nice and smooth in a simple demo runs very slowly on a real website, introduces visual artefacts or even crashes the browser. Why does this happen? How do we fix it?


  • Watch out for the number and size of composite layers from the very beginning — especially ones created by implicit compositing. The “Layers” panel in your browser’s development tools is your best friend.
  • Modern browsers make heavy use of compositing not just for animation but to optimize the painting of page elements. For example, position: fixed and the iframe and video elements use compositing.
  • The size of compositing layers is likely be more important than the number of layers. In some cases, the browser will try to reduce the number of composite layers (see the “Layer Squashing” section of “GPU Accelerated Compositing in Chrome“); this prevents so-called “layer explosion” and reduces memory consumption, especially when layers have large intersections. But sometimes, such optimization has a negative impact, such as when a very large texture consumes much more memory than a few small layers. To bypass this optimization, I add a small, unique translateZ() value to each element, such as translateZ(0.0001px), translateZ(0.0002px), etc. The browser will determine that the elements lie on different planes in the 3D space and, thus, skip optimization.
  • You can’t just add transform: translateZ(0) or will-change: transform to any random element to virtually improve animation performance or to get rid of visual artifacts. GPU compositing has many drawbacks and tradeoffs to be considered. When not used sparingly, compositing will decrease overall performance at best, and crash browsers at worst.

Allow me to remind you of the big disclaimer: There is no official specification for GPU compositing, and each browser solves the same problems differently. Some sections of this article may become obsolete in a few months.

Performant Parallaxing

Love it or hate it, parallaxing is here to stay. When used judiciously it can add depth and subtlety to a web app. The problem, however, is that implementing parallaxing in a performant way can be challenging. In this article we’ll discuss a solution that is both performant and, just as importantly, works cross-browser.

An abstracted view of what a CSS parallax effect would look like if the layers were to lift up off of the screen, and you could step to the side to see them in three axes, with arrows showing the layers moving up and down along the X axis.


  • Don’t use scroll events or background-position to create parallax animations.
  • Use CSS 3D transforms to create a more accurate parallax effect.
  • For Mobile Safari use position: sticky to ensure that the parallax effect gets propagated.


Both Scott Kellum and Keith Clark have done significant work in the area of using CSS 3D to achieve parallax motion, and the technique they use is effectively this:

  • Set up a containing element to scroll with overflow-y: scroll (and probably overflow-x: hidden).
  • To that same element apply a perspective value, and a perspective-origin set to top left, or 0 0.
  • To the children of that element apply a translation in Z, and scale them back up to provide parallax motion without affecting their size on screen.

The CSS for this approach looks like so:

Code language: CSS

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);

Which assumes a snippet of HTML like this:

Code language: HTML

<div class="container”>
  <div class="parallax-child”></div>