As an experiment, I am trying to replicate the Sprite functionality of AS3 in JavaScript without using the canvas object. I thought that using absolutely positioned divs and manipulating their css properties would be a no brainer, however in Chrome the animation introduces strange artifacts (seemingly because of redraw issues).
I can not find what I am doing wrong? The code is, in fact, quite simple. Here are some points that I tried which didn't help:
- Using relatively positioned divs (as opposed to absolutely positioned.)
- Using margins (as opposed to top & left properties.)
- Appending objects directly to body (as opposed to appending to a container div.)
- Using setTimeout (as opposed to requestAnimationFrame)
You can see a simplified fiddle here: http://jsfiddle.net/BVJYJ/2/
EDIT: http://jsfiddle.net/BVJYJ/4/
And here you can see the artifacts on my browser:
This may be a bug in my setup (Windows 7 64 bit, Chrome 21.0.1180.75). No other browsers exhibit this behaviour. I'd greatly appreciate if someone could comment on what I could be doing wrong. I'm more curious about the reason behind this rather than a workaround btw. That said, every explanation is welcome. :)
EDIT: There was a bug in the sample code which resulted in using setTimeout even when I was under the impression that RAF was used. requestAnimationFrame solves the issue with basic transformation but the issue remains with CSS transformations such as rotation.
I had the same problem with my liteAccordion plugin. It can be fixed by setting the backface visibility to hidden on the element you're animating, as you can see here: http://jsfiddle.net/ZPQBp/1/
Some research shows that setTimeout
could cause issues due to various reasons. You really should use requestAnimationFrame
:
Timers are not accurate to the millisecond. Here are some common timer
resolutions1:
- Internet Explorer 8 and earlier have a timer resolution of 15.625ms
- Internet Explorer 9 and later have a timer resolution of 4ms. Firefox
- and Safari have a timer resolution of ~10ms.
- Chrome has a timer resolution of 4ms.
Internet Explorer prior to version 9 has a timer resolution of 15.625
ms1, so any value between 0 and 15 could be either 0 or 15 but
nothing else. Internet Explorer 9 improved timer resolution to 4 ms,
but that’s still not very specific when it comes to animations.
Chrome’s timer resolution is 4ms while Firefox and Safari’s is 10ms.
So even if you set your interval for optimum display, you’re still
only getting close to the timing you want.
Reference: http://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestanimationframe/
Also
setTimeout
doesn’t take into account what else is happening in the
browser. The page could be hidden behind a tab, hogging your CPU when
it doesn’t need to, or the animation itself could have been scrolled
off the page making the update call again unnecessary. Chrome does
throttle setInterval and setTimeout to 1fps in hidden tabs, but this
isn’t to be relied upon for all browsers.
Secondly, setTimeout
only updates the screen when it wants to, not
when the computer is able to. That means your poor browser has to
juggle redrawing the animation whilst redrawing the whole screen, and
if your animation frame rate is not in synchronised with the redrawing
of your screen, it could take up more processing power. That means
higher CPU usage and your computer’s fan kicking in, or draining the
battery on your mobile device. Nicolas Zakas does an excellent job
explaining the impact timer resolution has on animation in a related
article.
Reference: http://creativejs.com/resources/requestanimationframe/
It has something to do with subpixel positioning. If you round off to the nearest pixel you won't see those rendering errors:
thisRef.block.style.left = Math.round((x + (mouseX - ox - x) * .125)) + "px";
thisRef.block.style.top = Math.round((y + (mouseY - oy - y) * .125)) + "px";