Webkit will render an element differently when the responsibility of painting it is passed from the CPU to the GPU. This switch can trigger unintended rendering artefacts which, although most obvious in text, apply to all types of content.

Changes in antialiasing

With the exception of IE11, every browser I tested uses subpixel antialiasing on the CPU. If an element is promoted to the GPU in current versions of Chrome, Safari or Opera then you lose subpixel antialiasing and text is rendered using the greyscale method. Firefox 30 retains subpixel antialiasing when an element is promoted to the GPU, while IE11 appears to use the greyscale method for both CPU and GPU.

Text rendered by the GPU doesn't have subpixel antialiasing in webkit browsers

In truth, the switch between these antialiasing methods in webkit browsers is hardly noticeable. However, if you are applying an animation or transition to the element and you do notice a subtle rendering change, you can force the CPU to match the GPU greyscale antialiasing like this:

#box {
    -webkit-font-smoothing: antialiased;
}

Content blurring

Blurred content is a webkit specific side effect that occurs when a GPU promoted element is rendered on a non-integer boundary. The effect is most often noticed if hardware acceleration is triggered by applying an animation or transition but it can also be triggered with a simple transform. Let's look at an example:

#box {
    position: absolute;
    top: 500px;
    -webkit-transform: translateY(-50%) translateZ(0);
}

Note: in this example I'm using translateZ(0) to force hardware acceleration.

Here the element is positioned 500px from the top of its container (for simplicity let's assume the container has a top and left offset of 0). The transform: translateY(-50%) declaration pulls the element up by half its height, so if the element is 20px high its effective top would be 490px (top – height / 2) and its bottom would be 510px (top + height / 2). The vertical rendering of the element would occur in pixels 490 through 510. In this state the effective top/bottom of the element resolves to an integer value which means it will look identical (except for antialiasing change discussed above) when rendered on the CPU or GPU.

CPU and GPU rendering are almost identical when an element is positioned on integer boundaries.

If the element is 21px high its effective top would be 489.5px and bottom would be 510.5px. Webkit browsers don't appear to round pixel values down so the texture has to be resampled using bilinear filtering (or similar) to account for the half-pixel boundaries – it's this resampling that causes the blurring effect. The 21 pixel high element now touches pixels 489 through 511 which means 22 pixels need to be painted. Here's what that looks like.

Blurring occurs when an element is positioned on non-integer boundaries.

Notice the halo around the text and the lack of clarity in the 'e' character? These rendering artefacts are also exhibited when the element is translated along the x-axis. Things are made worse if both the X and Y translations resolve to non-integer values – in this case you will see the blurring along both axis resulting in a further loss of clarity.

Blurring effect is exacerbated if both x and y positions are non-integer

Can it be fixed?

Sort of. It's possible to counter the blurring effect using JavaScript to ensure affected elements have a width and height that are divisible by 2. This can be achieved with something as simple as:

var elem = document.getElementById("box");
elem.style.height = (Math.ceil(elem.offsetHeight / 2) * 2) + "px";
elem.style.width = (Math.ceil(elem.offsetWidth / 2) * 2) + "px";

Simple? Yes. Practical? No. Forcing a width and height will fix the blurring issue but the element will no longer reflow if the viewport changes size. Sure, you could listen for the window resize event and clear the width and height style properties to allow reflow before setting them again, but what happens if the content of the element changes, if the user changes the text size or if you have to handle any of the other countless edge cases?

Summary

For now the only real solution is to avoid hardware accelerating text layers if you can't guarantee their dimensions will resolve to integer values. Unfortunately that also means avoiding animations and transitions that trigger hardware acceleration.

Personal Achievements

  • 2017 Web Designer Magazine: CSS VR interview
  • 2015 JS1k – winner
  • 2014 Net Awards: Demo of the year – winner
  • 2014 Net Awards: Developer of the year – longlist
  • 2013 Public speaking for the first time
  • 2011 .net Magazine innovation of the year – shortlist

Referenced in…

Smashing CSS, CSS3 for web designers, Programming 3D Applications with HTML5 and WebGL and more.

My work is referenced in a number of industry publications, including books and magazines.