Ugly aliasing in Chrome+FF on animated transform: rotate

Animated:

Static transform: rotate:

Example HTML:

<!doctype html>
<html>
	<head>
		<style type="text/css">
			img {
				width: 80px;
				height: 80px;
				animation-name: spin;
				animation-duration: 2000ms;
				animation-iteration-count: infinite;
				animation-timing-function: ease-in-out;
			}

			@keyframes spin {
			    from {transform:rotate(0deg);}
			    to {transform:rotate(360deg);}
			}
		</style>
	</head>
	<body>
		<img src="http://design-guidelines.web.cern.ch/sites/design-guidelines.web.cern.ch/files/u6/CERN-logo.jpg" />
	</body>
</html>

If instead of the animation, you just do img { translate: rotate(40deg); }, the aliasing looks much better. How can you fix that for animations? Looks fine in Safari. Lots of search results mention backface-visibility, but that only seems to solve problems along the edges of the images, if anything. No effect on internal lines like in the example logo.

Seems to be exacerbated by scaling down a high-ish-DPI image.

I’m sure the downscaling of the image is a big factor with this. You’re not going to get the filtering applied to the image during the animation to optimize performance (or just as a consequence of having the element in a hardware layer) and when dealing with an image at a smaller size than the original, the difference will be noticeable. It can be noticeable without downscaling too, but not as much. You found a pretty obvious example, especially with a line drawing/logo. Note that you can see the same thing happen with a static image just by applying a transform:translateZ(0); This doesn’t change the image at all, but it does put it on a gpu layer. Doing this can help you with browser composition issues or help you with performance when animating via JS which generally won’t see the benefits of a hardware accelerated layer.

1 Like

Yeah, animating it with JS fixes the aliasing problem:

var s = (new Date).getTime()
requestAnimationFrame(function callee(){
    document.querySelector('img').style.transform = 'rotate(' +((s - (new Date).getTime()) / 5 % 360) + 'deg)'
    requestAnimationFrame(callee)
})

Nobody (*cough* @kirupa *cough*) mentions some of these random pitfalls when pitching CSS animations.

But then I’d have to reimplement ease-in-out and such which is extra code I shouldn’t have to care about when I just want a clean render.

You’re not going to get the filtering applied to the image during the animation to optimize performance (or just as a consequence of having the element in a hardware layer)

Well, you might not. I think Safari applies it. And it’s not like the GPU can’t do anti-aliasing and such… I assume that setting in video games does something. But it’s weird that the browser makes this performance choice in weirdly inconsistent ways.

1 Like

This is an interesting gotcha! I didn’t know about that issue at all, nor did I know that making the same animation in JS would fix it! :smile:

As for the easing function, it isn’t too hard to implement. There are some examples here: http://www.kirupa.com/html5/animating_with_easing_functions_in_javascript.htm

Hmm, yeah. I probably phrased my complaint wrong, because I have read that article of yours before! I know the code is available and fairly simple for each easing function, but sometimes I’ll want to keep it out of my own source control if it’s not code that I wrote. This sort of ties in with my other thread about babel-polyfill.

Plus the situation seems just right for CSS animations. I’d just want an anti-aliasing: 4x CSS rule to apply to my image, or something. JS is just a fallback for when CSS doesn’t give enough flexibility to specify what I want rendered. If I were doing more complex animation in JS and wanted that sort of easing, I’d definitely reach for the code in that article right away.

Completely fair point :smile:

1 Like

There’s also an image-rendering css property that relates to this behavior. It has some control over the whether or not filtering gets applied to your images, but its mostly best used for preventing it from happening at all. The default is “auto” but there is nothing that says something like “always”, though an older implementation or maybe it was some other brow… (checking)… yeah it looks like Firefox has an implementation of “optimizeSpeed” and “optimizeQuality” which could potentially mean always do it or never, but it still alludes to allowing the browser to ultimately decide.

There’s no penalty in not applying a filter, so if you want pixel art to remain pixely on an upscale, this should be dependable (if implemented). When you want filtering applied for quality always, it doesn’t really help that much - unless a browser implements auto to do it always, which according to the link might be the intention of the spec (I didn’t read it in detail) but I wouldn’t bet on it, since I think for the default behavior the browser would want to optimize for performance and selectively apply it as needed.

I’m not seeing it now, but I think some browsers turned it off when scrolling too. So you’d scroll down a little bit, stop, and then see image quality kick in with some smoothing applied (they throttled turning it off in case something else happened right after which would cause it to revert again).

1 Like