Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to cycle through a large number of fixed position images in WebKit efficiently?

I'm currently working on a little site for my family. One of the things I wanted to do was to make a basic 'making of' stop-motion video. I could assemble it and upload it to Vimeo or something but I thought it was a perfect opportunity to use nothing but HTML, CSS, and Javascript.

I've got everything styled and my JS is working, etc. except that it performs atrociously in Chrome and Safari. Interestingly, it works great in Firefox and I'm not supporting it yet in IE. I'm hoping for 8 to 12 frames per second, with music playing, which I haven't bothered trying yet due to this. Bad performance is anything less than that. Currently I'm getting roughly 3 fps in Firefox (acceptable, but not what I was looking for) and in Chrome and Safari I'm getting roughly .6795 fps.

When running the Chrome Profiler, I get the following (relevant) output.

99.96%   99.96%     (program)
0.03%    0.03%      (garbage collector)
0.01%    0.01%      script.js:5:nextSlide

I've never used the Profiler before but I believe this is showing me that my JS is not what's hitting the performance so hard.

I've published a test page that documents the performance differences that you can visit with Chrome and Firefox.

I've also discovered that this seems to be related to the images cycled. Cycling different, simpler images seems to work just fine in both Chrome and Firefox, despite the fact that Chrome is still a little more power hungry than Firefox.

As further proof of at least this conclusion, though it's entirely unacceptable, is demonstrated here, after running the images through convert -compress JPEG -quality 1. They cycle much more efficiently, but of course the quality is terrible.

I have run these test pages in Chrome (16.0.912.63), Safari (5.1.2 (6534.52.7)), WebKit nightly (Version 5.1.2 (6534.52.7, r102985)), and Mobile Safari (latest as of 2011/12/28) and only Mobile Safari performs as well as FireFox. The desktop browsers were tested on a MacBook Pro.

2.7 GHz Intel Core i7
8 GB 1333 MHz DDR3

Interestingly, Mobile Safari on an iPad 2 performs as well as FireFox when rendering the test page. Though Mobile Safari is based on WebKit, in this instance it performs entirely different.

Decreasing the setTimeout call to 144 from 244 also seems to not do anything. I've arrived at 244 entirely arbitrarily at this point as it became clear early on that the timing of the display compared to the call didn't seem to correspond nearly directly. This leads me to believe that I'm rendering the slide show as quickly as I can on each browser.

So my question is, how can I make this performant in WebKit?

like image 244
Tim Visher Avatar asked Dec 15 '11 17:12

Tim Visher


1 Answers

You can debug the page performance in Chrome using the Timeline tab under the Chrome developer tools. The problem with your script is that your repaint cycle is simply too expensive, it currently takes 1.35s to repaint every frame.

enter image description here

The bad performance has nothing to do with the quality of the jpeg images (although the image quality also affects the page render time). The problem is that you are updating the z-index which causes the Chrome to repaint all images instead of just the next frame (You have a O(n) image slider website!).

The browsers try to do the minimal possible actions in response to a change e.g.: changes to an elements color will cause only repaint of the element.

Changing the element z-index property is basically the same as removing a node from the tree and adding another node to it. This will cause layout and repaint of the element, its children and possibly siblings. My guess is that in Chrome, the siblings are being repainted too, this explains the horrible performance.

A way to fix this problem is to update the opacity property instead of the z-index. Unlike the z-index, the opacity does not modifies the DOM tree. It only tells the render to ignore that element. The element is still 'physically' present in the DOM. That means that only one element gets repainted and not all siblings and children.

This simple changes in your CSS should do the trick:

.making-of .slide#slide-top {
   opacity: 1;
   /* z-index: 5000; */
}


.making-of .slide {
   position: fixed;
   /* z-index: 4000; */
   opacity: 0;
   ....
}

And this is the result, the repaint went from 1.35s to 1ms:

enter image description here

EDIT:

Here is a jsfiddle using the opacity solution, I also added CSS3 transitions (just for fun!)

http://jsfiddle.net/KN7Y5/3/

More info on how the browser rendering works:

http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/

like image 124
Cesar Canassa Avatar answered Oct 19 '22 22:10

Cesar Canassa