Tip:
Highlight text to annotate it
X
PAUL IRISH: Well, hello.
My name is Paul Irish, and I wanted to share some things
with you about performance, specifically HTML5, CSS3,
DOM-type stuff about performance, things happening
in the front end, the browser, things that could be slow and
could be faster.
And so we're going to talk about these kind of things.
First we're going to talk about reflows, hardware
accelerated CSS, animation, web workers, benchmarking, and
a little bit about build scripts.
Sound good?
Great.
OK reflows, let's just look at this diagram.
This is a diagram put together by Stoyan Stefanov.
When we're talking about reflows, we're talking about
the render tree.
That's the really important part.
And the render tree is made from--
well, it comes from two different things.
So we have the DOM tree.
The DOM tree is the result after you tokenize and parse
your HTML markup.
It gets parsed into the DOM.
And that's what you see in Chrome Developer Tools or
Firebug, all the elements, right?
Of course.
At the same time, we have styles coming in.
And so we have styles coming in from the style
sheets and so on.
And when those two combine, we get the render tree.
And the difference between the DOM tree and the render tree
is that the render tree--
let's say that we have an element that is display:none.
Not present in the render tree.
So it's in the DOM tree, but it's not in the render tree
because we don't see it.
It's just not even there.
Or if we manipulate the window size or change the font size,
the full-page zoom, that, of course, changes the render
tree most of the time, whereas the DOM probably is staying
the exact same.
So the render tree is what you see, and the DOM
is underneath it.
A reflow is a change that necessitates a recalculation
of the render tree.
And it might recalculate just the very bottom of it.
But it might recalculate the entire thing.
And reflows can be cheap.
But at the same time, they can be expensive.
Specifically on mobile devices or old IE, they can be quite
expensive, and you don't want to do a reflow if
you don't have to.
And there's ways to avoid them.
So first, well, let me show you this.
This right here--
ah, this is so cool.
This is a debug version of Firefox, I believe.
And it's actually laying out the mozilla.org homepage,
calculating the layout and the position of absolutely
everything.
This is awesome.
It figured out where it all goes.
And then once it's all done and the browser's figured out
it, there we go.
Pretty cool.
But let me show you this one part in here.
It's right about here--
finish off the footer.
And then all of a sudden, something happens.
And then it has to recalculate the whole
thing all over again.
And I don't know why that happened, but
it didn't need to.
And we were basically done at this point.
And now we have to wait another 30% of our time so
that it can recalculate the layout of absolutely
everything when it should have been done.
And so this is the kind of thing that we want to avoid.
We don't want to have a full page reflow that invalidates
the entire render tree.
We want to get that finalized render tree out to the user as
fast as possible so that they can interact with your DOM.
So what causes reflows?
A lot of people have wondered.
And a lot of people have thought, well, checking the
element.offsetWidth is normally the trick.
But Tony G. On the Chrome team dug into this and
tried to answer it.
So Tony dug in, just opened up Google Code Search and looked
for Update Layout, Ignore Pending Style Sheets--
and this is essentially what does a reflow in WebKit--
and found all these items, and then just listed them out.
So what we get is this.
These are all the properties and methods that
will cause a reflow.
And so the offsetWidth, which is kind of the one that most
people know, that's certainly here.
But there's a lot of other ones.
And interestingly, innerText appears to cause a reflow
whereas innerHTML does not.
I'm not exactly sure about that.
scrollTo on the window does.
So these are all the things that by calling, you would
trigger a reflow.
But what are the kind of typical operations
that would do it?
Of course, manipulating the DOM tree is definitely going
to cause a reflow.
Yeah, definitely.
If you hide a DOM node with display:none, that will
manipulate the render tree.
It will cause a reflow.
But then any changes to that element once it's
display:noned won't.
Whereas over with visibility:hidden, if you just
take an element, visibility:hidden it, there's
no recalculation of the page.
The geometry of the page stays the same.
So actually there's no reflow.
We just repaint that one little box and keep going.
If we're animating anything across the page, of course
we're doing reflows.
It depends on maybe you could overflow:hidden it, a
container, so that it's kind of encapsulated a little bit.
Adding new styles, and then any sort of user interaction
like resizing the window, changing the font size, even
scrolling causes reflows.
So here's a little example about a small optimization
that you could do to avoid causing excess reflows.
So what we're doing, this is just some simple little jQuery
code, right?
We are grabbing an element, and we're just grabbing the
left style property off of it.
And then we're going to take that left and apply it to
something else.
And then we have to do that to another one as well.
So we get it here, set it, get it here, and set it.
The problem here is that actually checking our style
property is going to trigger a recalculation of
the styles in a reflow.
And because we set something here, it has to
do it all over again.
So a way to avoid this is we can just bunch our gets
together and then our sets together.
I'll show you.
This is kind of what it looks like over in the Chrome Dev
Tools Timeline view.
So here we are.
We have Recalculate Style and Layout.
And these kind of go together.
Recalculate Style is essentially the prerequisite
for a reflow.
In WebKit and in other browsers, a
reflow is called a layout.
Mozilla uses the term "reflow." It's just
terminology really.
What you'll see here is that in that first example, we kind
of do this thing twice before we're able to paint out what
the changes are.
Whereas in the second one when we bunch them together, we can
get away with just doing a single reflow
before we're all set.
And then we can paint it out.
And I will point out the scale looks a little weird here.
But we finished the second time at about 3 milliseconds.
Whereas the first time we finished maybe about 4 to 4
and a 1/2 milliseconds.
So the third one, in addition to having less reflows, is
actually indeed faster.
So there's a few strategies for avoiding reflows.
The first is batching your DOM changes together
like we just did.
Other ways we can do it is you can clone the existing
element, work off DOM with that element, and then--
because any manipulations while it's off DOM are not
going to cause a reflow.
And then once you're done manipulating it, you can
replace that one that was already there and check the
new one in.
If you hide anything with display:none, it
will cause a reflow.
But if you then manipulate it, all those manipulations while
it's display:none will not cause reflows, except in IE.
However, you can take an element.
Instead of doing display:none, you can just take it off DOM,
hold it in documentFragment, do your manipulations in
documentFragment, and then put it back in when you're done.
You only get the reflow when you are manipulating the
actual DOM.
And when things are off DOM, you're fine.
So that one you're fine in IE and everywhere else.
Cool?
OK, there's a few more resources I would like you to
just check out if you're into this stuff.
Stoyan Stefanov has done a lot of research
on reflows and repaints.
and so he's got some great stuff here.
BrowserScope has a few tests to see what triggers reflows.
Like we were just seeing, there's a lot of small details
that are different across browsers.
David Baron had a really great Google Tech Talk about browser
internals and specifically aiming that talk at web
developers.
And so this is, I think, an hour-long talk that you
should check out.
And WebKit, about five years ago, did a five-part series
about how rendering works.
And it's so cool.
It's from five years ago.
But it's totally relevant today.
I definitely recommend checking that out.
OK, I think we're done with reflow.
Let's move on.
Hardware accelerated CSS, yeah.
All right, getting into some sexy here.
So we're just going to skip right into the demo.
This is Isotope by David DeSandro.
Let's see, I'm going to change the sort a little bit.
And then I'm going to insert some new elements.
Click around here.
Yeah, like this.
This is good.
Even resize the window, and that responds really nicely.
So what's actually happening here is that we're actually
using CSS transitions and CSS
transforms to do these changes.
Now, normally you might think you're going to manipulate the
position of where things go.
And you'll probably be like, oh, it'll be absolutely
positioned.
And then I'll just manipulate left and top and tell it where
it's going to go now.
The trick here is that we're actually using
2D transforms translate.
So we're using webkit-transform:translate,
and then we're just changing the x and y of translate.
Then we're also using a CSS transition.
And as a result, we're able to get all these operations
hardware accelerated, which means super-high frame rates,
really high-fidelity animations, and they look
basically as good as they can.
You're writing it in a declarative way so that the
browser can be like, oh, yeah, I got this.
Let me take this.
I'm going to take this down to my friend the GPU and optimize
this for you.
And so it's going to look really good.
The cool thing here in Isotope is that David wrote it in a
way that it can detect support for transforms and
transitions.
And if there's not support, then it'll go back to a
jQuery-based animation style.
So if your browser is new and good, we're going to do the
right thing.
And if it's a bit older, it'll still look OK, but not as high
fidelity as possible.
So this is kind of the power of animations of
hardware-accelerated CSS.
Now, those were 2D transforms, and everything was just moving
around on a 2D plane.
And normally, 2D transforms and operations on the 2D plane
don't get hardware-accelerated by browsers and by WebKit
specifically.
But there's a trick.
And there's a trick to make the browser do it.
Let me show this clip from Remy Sharp.
[VIDEO PLAYBACK]
-OK what I'm about to show you is a CSS animation.
And it's engaged using JavaScript and just the normal
touch gestures.
But the first effect I'm going to show you now is one without
a 3D transform.
So this is just doing a scale and nothing else.
And you see it's just a little bit jumpy.
That's the actual animation jumping.
And if I show you the same version but just with the
translate3d set on the element that I'm zooming, you can see
it's completely smooth.
And all I've done is said the image--
so the image element that I'm pinching and zooming on, I've
just added a CSS property to say translate 3D.
[END VIDEO PLAYBACK]
Yeah, so that's it.
It's kind of a total hack.
You can use either WebKit transform, translateZ(0) or do
translate3d.
Depending on your use case, one will probably make more
sense for you.
And all of a sudden, the browser's like, oh, cool.
Let's throw this on the GPU.
And as a result, we can get much higher-fidelity
manipulation on the 2D plane, just like
we saw in this video.
It's pretty cool.
There's a few really good ways when you're working with this
kind of stuff to debug it a little bit better.
Over in Safari, there's a development flag to turn on
some colors.
This actually stands for core animation.
I'm going to load this up over the iPhone simulator.
And I'm actually going to load this page.
This is a tutorial on HTML5Rocks by Malte Ubl.
And we're going to show this little demo here.
And what we're actually doing is we're detecting support for
CSS transitions and transforms.
And if we have support, we're just going to use a transform
along with the transition.
Otherwise, we use jQuery animate.
So here in Chrome, we're using a transition transform.
And you see that moves across the screen.
I'm going to show this in the iPhone simulator.
You can see the OS is accelerated.
Scroll down, here we go.
So I'm just going to click.
And you'll see-- yeah.
And the trick here is that we actually use that
translateZ(0)--
we call it the silver bullet here--
trick to make that hardware acceleration happen.
Over in Chrome, we have a somewhat similar thing.
Over in about:flags, we have this setting.
And let me bring this up.
In about:flags, there's a bunch of really crazy stuff.
I recommend you dig into this some time.
We have composited render layer borders.
And well, let me just show you what it does.
Here's this presentation that we've been looking at but with
this setting turned on.
And you can see we get borders around of all the layers that
are composited.
Pretty cool.
So this helps a lot to just verify that yes, this actually
is being composited on the GPU.
And then also in this 3D case, we have a nice little debug
view of what's going on.
I will point out that this used to happen as a
command line flag.
So it didn't used to be nice and handy in this about:flags.
But yes, it used to have to run the Chrome executable with
a command line flag.
And this page over on Peter Beverloo's site maintains a
list of all the active command line switches or flags.
And so I recommend you checking this out.
One of the ones in here is a show-paint-rects.
And so this actually draws little borders whenever a
section of the DOM gets repainted.
Why that's interesting is because you want to know--
kind of when we were talking about reflows, you want to
know exactly how much of the DOM is being invalidated or
the render tree is being invalidated
when there's a change.
And so with show-paint-rects, you can see that when you make
a change, only the part that needs to be recalculated and
repainted is being changed rather than a much larger
chunk, which would be kind of unexpected.
So show-paint-rects is another very helpful flag to use when
you're debugging some of this advanced rendering stuff.
This tutorial has a number of other recommendations around
graphics optimizations that you can make and also digs
into JavaScript profiling.
So dig into that a little bit more if you're looking to eek
out some better performance of your HTML5 app and these
recommendations are not good enough for you.
But next we're going to dig into requestAnimationFrame.
So requestAnimationFrame basically came about because
browsers were like, hey, we know that you're doing
animation in the browser.
And we know that you're just abusing setTimeout and
setInterval loops to do it.
And that's not cool.
It's not really cool.
Because you know what?
A browser can optimize animation better than you
writing a setInterval loop, just trying to peg the CPU as
fast as it will go.
Browser is smarter.
So one of the things that it can do is it can batch things
into a single reflow and repaint cycle.
So let's say that you had a JavaScript-based animation.
You had an absolute position element, and you're updating
the left and top or something like that.
The browser can now synchronize that along with
the concurrently running CSS transition.
Or if you had some SVG SMIL animation running at the same
time, it can kind of make sure that those are all going at
the same time.
It's going to optimize it for 60 frames per second, which is
the refresh rate of most displays.
And so you're going to get the best frame rate possible.
60 Hertz is kind of the perfect target for everything
that you want going on in the browser.
And the really cool thing is that you're really friendly to
your users' batteries.
Because what happens is that if the tab is not visible, it
gets skipped.
So let me load this up right here.
This is a--
whoa!
Yeah, OK, here we go.
So we got this WebGL demo.
And you see here we got a lot of action over on the GPU.
And this is the JavaScript running inside this page.
It's pretty heavy.
Gonna move this around.
Now I'm just going to switch to another tab.
You see that instantly dropped.
Because the requestAnimationFrame is being
used, the browser is like, hey, you're not even
viewing this tab.
We don't need to be wasting the browser, the CPU, the GPU
in order to make this happen because we know that they're
not looking at it.
Whereas if this field demo had been using a timer loop, it'd
still be going.
It would still be eating down my battery here.
So I bring that back up.
And our use of the CPU returns.
So if you're doing any sort of animation,
use this if you can.
This actually just landed in jQuery.
So this is going to be in jQuery 1.6.
And it's also in Grit.
And it's in a few other libraries at this point.
So I definitely recommend it.
This is available in both Chrome and also in
Firefox, Firefox 4.
Over on my blog, we have a nice little shim layer to
handle this.
We basically feature detect to see if the
native support is present.
And if not, we're going to use a setTimeout loop that
optimizes for 60 frames per second.
And that's how you use it.
Cool.
Web Workers, the idea here--
and I don't want to cover this too much.
The idea with Web Workers that you're probably familiar with
is we want to move heavy computation, expensive tasks
outside of the UI thread.
Pretty much all JavaScript runs in the UI thread.
And if something is taking a long time, the user can't
interact with the page.
The page becomes non-responsive.
And in the case of Chrome, we get a sad, freezing cold tab.
He's so cold.
And we don't want that.
No, that's bad.
And we want to avoid that.
And without Web Workers, we can.
I'm going to show this.
This is a little bit more advanced use of Web Worker.
But I think it's really cool.
First, normally the way that you instantiate a brand-new
Web Worker is you would be like, new Worker,
myworker.js, there.
This is where the code is going now.
You pass it a file name.
And then it goes and gets that file.
And then it uses that file.
And that's your worker.
It has its limitations.
This here is an example of creating a
worker on the fly, basically.
So here we have-- oops--
our worker code, and it's a script of type worker.
So this is going to get skipped by the browser.
So we're just kind of using it to hold some text right now.
And then down here on our script, we're going to
use the file API.
And we're going to use BlobBuilder, create a new
BlobBuilder.
Then we're going to grab the text of this script node,
throw that into the blob.
And then when we create our worker, we're going to use
URL.createobject URL with the blob, and check that in.
Essentially, we create a file on the fly and then
instantiate the worker with that.
And so now we have the advantage of having a little
bit more control over the worker in case we want to
change it or something like that.
And then also we avoid an HTTP run trip.
So from a network layer, we're a little bit more
optimized as well.
So that is just kind of a cool example of where you can go
with workers combined with the file API.
Some places where you might want to use workers, text
formatting of a long document, syntax highlighting maybe,
maybe not, depends on your use case.
If you're doing heavy image processing or dealing with
large arrays, also the audio API, for instance, the WebKit
Web Audio API or the Mozilla Audio Data API, depending on
kind of the analysis that you're running, a worker might
make sense.
Also, the FileSystem API, which is available in Chrome,
has had support for workers for a while now.
And this is really good.
You might be dealing with large binary data, and you
don't want that holding up the UI thread at all.
You want to just let that happen as it does.
And so letting Web Workers handle the interaction with
the file system is the right way to go there.
Cool.
Want to touch on JSPERF real quick.
Yeah, this is real cool.
So JSPERF, if you don't know, is a site
for performance tests.
And so we have a few things going on here.
We have a nice human readable URL.
And oftentimes, with a URL like join-concat, this is now
the definitive join versus concat string
concatenation test.
And there's revisions as well.
So within the page we have our variations of the code that we
want to test.
So this is the age-old concatenation with plus equals
or array.join, which is faster.
And I believe it's IE 6 and 7 are faster with the array
join, and absolutely everyone else is faster with a
straight-up concatenation augmented in a string.
So don't use a array.join anymore.
But the nice thing here is that we can run the test and
get our results and figure out kind of what it is.
And so the site is used by a lot of people.
In fact, jQuery has been using it for the
library development recently.
So let's say there's a few different
variations of DOM traversal.
The nice thing here is that we can do our tests with a few
variations, kind of see what's going on, and then the results
get reported to BrowserScope, which kind of handles what
user agent it's coming from, what are the numbers, and kind
of give us a really comprehensive table of what
the story is across all browsers and where is each
thing faster.
And so I'm running it here.
This is WebKit Nightly.
And we see, well, in this case, we have these traversal,
and we have some different results.
So JSPERF is really good.
I like it a lot because it's kind of like the clearinghouse
of performance best practices.
People will say, oh, well, you should use triple equals
instead of double equals because it's faster.
And now we can actually find out, hey, are they right?
And are they right across all browsers or not?
It's powered by benchmark.js, which is a very finely tuned
and calibrated benchmarking script that works not only in
the browser but in Node and Narwhal and Ringo and all
these different environments and is definitely the right
way to be benchmarking different variations of code.
So a quick touch on the HTML5 Boilerplate.
It's a project that I work with.
And recently, we put out a build tool.
The build script is just an Ant script.
But the cool thing is that when you use it alongside one
of the web server configurations, like for
Apache or Nginx, you're basically going to get a 90%
on your score for Google PageSpeed or YSlow.
There's all these things that you've heard.
And you're supposed to be doing like combining and
minifying your JavaScript, your CSS, your image
optimization.
And the script does all these things.
It also does HTML minification.
It revs your file names so that we can cachebust when
there's a new version.
And then we upgrade the web server configuration to use
far Future and Expires headers because now we have unique
file names.
So it does all these things for you.
And we can basically take what was a C and B grade website
and bring it up to some much higher scores.
So I definitely recommend you dig into that if you'd like to
not have to repeat all these performance optimizations
every time you make a site.
And let a script handle it for you.
All right, I guess that's it for me.
If you have any questions, you can leave a comment or hit me
up on Twitter.
I'd also recommend to check out html5rocks.com.
We have a lot of tutorials on there of really good stuff.
And we have a lot that are focused on performance and a
lot of the things that I've been talking about here, so
plenty more to read and learn there.
So I guess that's it.
Thank you very much for watching.