Animating a DIV with JavaScript renders artifacts

2020-05-19 04:56发布

问题:

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.

回答1:

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/



回答2:

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/



回答3:

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";