Essentials • 53:38
Great performance is an important component of any advanced website or web application, and is essential for content delivered to mobile devices like iPhone. Discover techniques and technologies for improving overall responsiveness. Learn how to minimize resource requests, streamline CSS, and use techniques such as image spriting to dramatically reduce page load time for your website or web application.
Speakers: Jeff Watkins, Ryan Orr
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon, everyone, and welcome to session 454 on Optimizing Websites. We've been talking to a lot of you this week, and we've heard lots of stories about websites not loading as fast as you'd like, using more bandwidth than you probably want, and also that you're feeling a lot of pressure to add more interactive features to your websites and your web applications. And you really want to know how you can get the most performance out of the Safari browser, and coincidentally, every other browser out there.
Well, my name is Jeff Watkins, and I'm going to be talking to you a little bit today about that. And with luck, maybe we'll answer all of your questions. If not, maybe we'll give you some food for thought. Today we're going to cover in depth the problems that are facing all of our websites. You know, we've talked to a lot of you, and you're facing the same problems that we are.
And We're also going to give you some really good strategies that you can use, that you can take back to your offices and use in optimizing your websites as well. And then we're going to run through a case study of what we're doing on the online store to put these strategies in place to really drive the performance of our site.
Lower our load times and decrease our bandwidth utilization. And then finally, we're going to run through some interactivity issues, how you can improve the performance of your JavaScript so that you can add more and more rich interactive features to your website. So let's step into the Wayback Machine and zoom all the way back to 1999 and take a look at a web page that really would have required no optimization if we were loading it today. I'm speaking, of course, of the home page of the online store, circa 1999 or so, judging by the clamshell iBook.
And it's no wonder this page would take almost no time for a browser to load. There's half-dozen to a dozen images. There's no CSS. There's no JavaScript. It's really a pretty static, simple page. Load this in Safari today, and it loads in a heartbeat. Now, if we fast-forward to today, this is the online store as of just prior to Monday, and it's a lot richer.
We're taking advantage of the fact that we're using the same old web page that we used to use in the old web page. We're taking advantage of the separation of presentation and content and a lot more interactive features, some of which we'll actually see today. And the problem is that we have 13 CSS files.
24 JavaScript files and a grand total of 80 images. Now, that's a total asset of 117 assets on the page. Now, you may be saying to yourself, so what, who cares? What really -- what penalty are we paying for having 117 assets on the page? And it turns out we're paying a pretty high penalty. If you were here yesterday and you got to see the debugging session for Safari, you're probably familiar with this already.
This is the resource panel in the new Web Inspector. And what this is telling us, when we load the home page of the online store, is that it takes 12 seconds and 841K just to get the home page. I don't know about you, but I consider that unacceptable.
So let's dive into this a little bit and try to understand where those numbers are coming from. Why is it taking 12 seconds and good Lord, where's the 841K come from? So in order to do that, I'm going to ask you to bear with me a second, and we're going to run through the anatomy of browser request. I know for a lot of you this is the sort of stuff you're already very familiar with, but there are two really important factors I want to talk about here.
The first thing that the browser has to do is it has to open the socket to the server, obviously, otherwise it's not going to be able to get any data back and forth. Then it sends its request over, and ultimately it gets some data from the server. Really, like I said, this is introductory stuff. But there are two things that often get overlooked here.
The first is latency, and the second is overhead. Now, latency is the time from when the browser makes its request and until it receives its first byte of data. So the first thing that the browser has to do is it has to be able to get the data back to the server. The second thing that the browser has to do is it has to be able to get the data back to the server. So the first thing that the browser has to do is it has to be able to get the data back to the server.
That's really, really hard to reduce. You know, if you've got a data center here in Cupertino and your visitors are over in Oslo, the packet's got to get here somehow, and that takes time. So on the store, we use an edge caching solution from Akamai, and that means that the cache is as close to you as we possibly can get it. So we see average latency of about 68 to 70 milliseconds. That's really great, but it's still 68 to 70 milliseconds. And for the home page, we see a total latency of two and a half seconds.
Now, that's time that the browser, great browser that it is, is just sitting there idling. It's not doing anything. It's not loading fancy pings. It's not caching its JavaScript. It's doing nothing. So we really want to drive that down a little bit more. And then the second big issue is overhead.
And that's the number of bytes that the browser sends to the server and the server sends back to the browser that's used simply to negotiate the transmission, saying, hey, give me this file. Okay, here's your file. Here's how big it is. Here's when it was last modified, so on and so forth. Now, that varies by browser to browser and server to server, and we see that average somewhere between 500 and 1,000 bytes. Obviously, if you have big cookies or other attributes that they're sending along, that could be even higher.
On the home page of the store, we see a total of about 63K. Now, that doesn't improve our page at all. It doesn't make for a better experience for you when you come to the store, and I'd get rid of it if I could. So let's take a look at, we're going to take a look at ways that we can do that.
This is all good. I mean, we've got 12 seconds. Fortunately, the resource inspector already includes latency, but it has no real way of, or it doesn't today, include overhead. So that boosts our total bandwidth usage up to 905K, which is just way too much. I mean, we're pushing a full megabyte here. But we've already seen that we're losing two and a half seconds to latency. And if you're on a residential cable connection or T1 or anything like that, you should be able to download a full megabyte in four to five seconds or so.
So I'm kind of curious where the other five seconds are going. And it turns out there are a number of roadblocks that stand between the browser and actually rendering your page, even if it's got all the data. Now, the first roadblock is that the browser won't render any elements until... All the CSS is loaded. Now, that means that if you told it you need these CSS files to know what color and what shape and what background to use for the nodes, obviously, it wants to wait until that information is available so that it can render your page correctly the first time.
Secondly, elements may not render until they're fully closed. Now, depending on your browser, this can be either good or bad. And if you have like a highly position-dependent layout, so you've got floats with absolute positioning and all sorts of fun stuff, the browser may honestly not know where to put the node until it's all done laying out the node.
Now, as we'll see, this gets exacerbated by the next problem, which is that the browser stops rendering while it's processing a script element. And the reason for this is really simple. And that's because in the old days, and to some degree still today, coders could call document.write and modify the file as it's being parsed.
Now, naturally, if you're going to modify the file as it's parsing, the browser makes an optimization to say, hey, I'm not going to keep parsing because... Jeff Watkins, Ryan Orr ...if you change the document, I'm going to have to rewind and reparse. Safari 4 is doing a...actually, it's in the...some of the recent web kits are doing a great job of spinning off a secondary parser to keep parsing and keep loading your files as an optimization. But we really can't count on that from all browsers.
And then finally, synchronous AJAX requests stop everything. I don't know if you've noticed on the online store, but as we're loading the page, you can't swap all the files. Jeff Watkins, Ryan Orr ...and then you can't swap all the files. So, we're going to have to switch off to another tab because we've brought your browser to a standstill while we're making all of our synchronous AJAX requests.
Now, from our perspective, we could see that as an advantageous thing because maybe you've got Amazon open in your comparison shopping. Well, you can't buy it from Amazon while you're waiting for us to load. Jeff Watkins, Ryan Orr ...but similarly, you can't go to Gmail. You can't do pretty much anything while you're waiting for our page to load. And ultimately, we come to be seen as the culprit. So, we don't want to be the culprit.
Right? So, let's take a look at how these things play on the store's homepage because it turns out they're directly responsible for our total load time. We've got 13 CSS files, as I said. Well, every single one of them has to be fetched and parsed and the entire CSS structure has to be rendered or put into memory before a single node in the body will be rendered. That's 13 CSS files.
Next, we've got two script elements with external sources. So, we have to fetch those files, wait for them to load, parse them, execute them, and it turns out that those two script files fire off 10 synchronous AJAX requests. So, before we've even gotten to the body of the document, we've caused the browser to come to a complete and screeching halt 12 times.
I don't know about you, but I'm beginning to see where this 12 seconds is coming from. It doesn't get any better, sadly, once we get into the body, because we have an additional four script elements with external sources. Okay. Again, four more files that have to be loaded, parsed, and executed. Seven inline scripts.
So this is great. We don't have to fetch anything, but we still have to parse it and we still have to execute it. And somewhere in those 11 scripts, we fire off seven more synchronous AJAX requests. So before we've gotten to the end of the document, we've caused the browser to come to a halt another 18 times.
I'm actually surprised it only takes 12 seconds to load. And in fact, the truth is, I frequently see it at home take 30 to 40 seconds. So I'm a little unhappy about that. Well, what can we do about it? Turns out there's three things that we're going to do about these problems. The first one, is going to minimize the number of assets. We're going to take the JavaScript and the CSS and compact it down into single files.
Next, we're going to shrink the total asset size. So it's great that we have fewer assets going out, so we're paying less of a latency and overhead tax, but we're going to shrink that down so they download even faster. Then we're going to look at a couple of ways that we can reduce the script impact. So ways that we can either fool you into not noticing that it takes time for the scripts to execute, or do the work in the scripts at a later point so that the page renders faster.
It turns out that minimizing the number of text assets is actually pretty easy. You know, we've all been there. We start off and we've got a couple of CSS files, maybe a couple of JavaScript files. You hire a couple of guys to help out and work on the team. Suddenly you've got 10 CSS files because they're spread out over different parts of the site, maybe compartmentalized for different portions of the pages. Your JavaScript gets more interactive.
So suddenly 10 years goes by and you've got 13 CSS files and 24 JavaScript files. And you're thinking to yourself, oh, my God, this is just killing me. We're not saying to you that you should take all of those files, put them back together in one file, and now you're going to work on like a 384K JavaScript file and everybody's going to have to tiptoe around in there.
No, really what you can do is you put together a script, and we'll see the details of that a little bit later, that at either build time or at, you know, at page request time, dynamically concatenates those files together. You vend out a single file, and as a result, you pay less of a latency tax and you pay less of a total overhead tax. So the result is you're paying those taxes only once for JavaScript and once for CSS.
[Transcript missing]
So let's take a look at how that would work if we were to build just the breadcrumb bra on the online store's homepage. Well, we're going to start off with our canvas, right? And we're going to pull in the left-hand edge, followed by the little home icon thingy, and then that weird triangle thing.
Slide all the way down to the end, and we're going to pick up the divider bar, the shopping cart, and then the final right-hand edge. So we're good. We've got all the image assets that we're going to need for the breadcrumb bar, except one, which is the actual background.
Well, it turns out, because we use a horizontally repeating background for the background of the bar, it needs to take up an entire row within our canvas. There can't be anything to the left or the right of it, because that would just get repeated as it fills out the bar.
Now, image sprites are not a cure-all. They're really excellent if you're designing a brand new site or you're going through a full remodel where you're taking everything down to the bare metal, redesigning it, and starting again. They can be a little bit troublesome to work into an existing site. The problem is that, first of all, they have to be an exact fit.
So if you have a background image, really all you're using here is a viewport onto that canvas, and you're moving the canvas such that the image that you're interested in appears within your node. Well, if your node is very big and the image that you're interested in is very small, well, the browser, except for Safari coming up, doesn't have any notion of clipping of the background, which I believe is a CSS3 property.
And as a result, all of the images next to your sprite will also appear in your node. So the solution for that is you have to leave white space in your canvas, which, depending on the size of your node, may kind of defeat the purpose of putting this image in the sprite.
So that's why this may not be the sort of solution that you can do automatically, say, with like a build script. This is the sort of thing that almost has to be hand-tuned. And finally, we already saw... We already saw that repeating images pose a special problem. Fortunately, this is actually an easy one to solve.
Basically, what you do is you have a separate canvas for horizontally repeating images and another canvas for vertically repeating images. And those wind up being, in the case of horizontal, tall, thin images, obviously, because they just repeat horizontally. And for vertically repeating, it's a nice, wide but short canvas. So now we've got fewer images on the page.
And what we really need to do next is make those images -- fewer images -- fewer assets on the page. What we need to do is now take those assets and just shrink them down. Make them take less time to actually load. Well, there's a good thing here. The assets don't need to be humanly readable. You and I all work -- and I know you're better than this than I am -- we comment our code.
We format it nicely. Well, it turns out that this CSS is exactly the same to the browser as that. Now, we don't want to work with this, but I'm perfectly happy serving this out to the browser. Similarly, this JavaScript is exactly the same to the browser as this JavaScript.
Could be a problem if you need to debug it, but that's an easy trick. You just swap out the file. So this will take our actual textual assets, strip out all the extraneous stuff, like comments and white space, and in the case of JavaScript, you know, some meaningful identifiers, and really reduce the size of the assets so they download a lot faster. Because they download faster, obviously the browser is going to be waiting less time, and your page will render faster.
Now, I promise you we're going to take a look at two ways that we're going to reduce the impact of script execution. Now, in order to do that, we have to understand what the browser is doing with each script. The first step it has to do, obviously, if it's an external script, it has to fetch it. Well, we're good here, right? Because we've reduced the number of scripts that we're including on our pages to possibly one, possibly two, if you have to send out a separate script for IE. So this should be not so much of a problem.
Well, then the browser has to parse it. Now, short of sending a brand-new Mac Pro to everyone who visits your site, and I promise I'll come visit your site if you send me one, there's really not much you can do to accelerate parsing of JavaScript. You pretty much just have to deal with it.
So, fortunately, Safari is really fast at this, so we can kind of ignore this as well. The last thing that we can affect is execution time. And there's two things we want to look at. The first is really kind of a trick. It's going to take advantage of the fact that it takes a moment for you to recognize that the content's even arrived.
So we're recommending that you put your script content at the end of your pages. Seems like a really obvious thing. But by doing that, the browser gets to go entirely through your document Jeff Watkins, Ryan Orr: So, I think the most important thing is to be able to use the DOM content loaded event, and if you are familiar with the DOM content loaded event, you're probably also familiar with the fact that it has some challenges, especially regarding IE, which doesn't support it at all, and older versions of Safari. Even though there are ways to get around this in your libraries, they don't always work 100%. This, on the other hand, does work 100%. By the time you get here, your page is ready. All of your nodes are available.
So that's the trick. The other one is a different sort of thing. You know, imagine you're out last night boozing it up, oops, sorry, having a good time, and you're
[Transcript missing]
Okay, I guarantee you, you probably don't need Fibonacci numbers on your website, but it really is a good introductory sample sort of thing. And it allows us to highlight this notion of IOU data.
So what we're going to set up here is a cache of our Fibonacci numbers. And when we first call get Fibonacci at index, we're going to notice, hey, the cache of underscore first 100 Fibonacci's is null. So the JavaScript will then call calculate first 100 Fibonacci's, which does some math, and then returns the cache. We then vend out the Fibonacci that you're interested in from that cache.
Now, the great thing about this is... is if you never need a Fibonacci number, you never pay this penalty of actually calculating them. So obviously, this is a fictitious example, but if you need to calculate, say, a CRC or some other computationally intensive table, this would be an excellent thing to apply to it, because if it turns out that you don't need it, you never pay the cost.
So at this point, I'd like to bring up Ryan Orr, who's a UI engineer on the store, and I'd like to ask him to run us through in detail some of the things we're doing on the store to take these strategies, put them in place, and more importantly, give you some sense that A, you can do it too, and B, what sort of performance improvements that you'll see from this.
Right? Hi, my name's Ryan Orr, and I'm a UI engineer on the online store. And, you know, Jeff spent the first part of this talk kind of going over some of the problems you all might be seeing on your various sites. And, you know, this talk was really bred out of the problems that we were seeing on the online store.
You know, a few months ago Jeff came to me and said, "Look, we need to get the page weight under control. We need to reduce the asset size, we need to reduce the time it takes to load the site. So I want you to go back with the team and kind of figure out some solutions to how we can kind of manage this problem." And so, you know, the team and I, we sat around and we said, well, you know, where do we start? There's so many areas that we could look at. Where do we begin? And so we decided that we would probably start looking at the source code.
And immediately we saw a page that looked like this. Link element after link element after link element. You know, and I'm sure you all have pages that are like this. And as Jeff mentioned earlier, you know, we pay a cost for each one of these assets that we request, right? And so if we can just get these under control and kind of vend out a single asset, we might be well on our way to furthering this.
And so here we decided well, we should probably concatenate this stuff. How should we do this? Do we want to event this on the fly? What do we want to do? And so ultimately we decided that we should probably do a prepackager. You know, some sort of beginning phase where we could grab all this stuff up, push it together, and then do some other processing on the files.
And so that's something that we did. You know, this can be written in any sort of language. You know, it could be written as a Bash script. It could--if you're a Ruby Python dev, you can use those languages. I chose Ruby because it's something that I'm very comfortable with, but anything really works.
So basically we have a simple YAML configuration file, very standard Ruby. There are YAML parsers for all various languages. But we have three headers in this YAML file. We have global, IE6, and IE7. And underneath each one of those headers, we have a list of CSS files in the same order that you would have the link elements on the page that we just saw, so that you get rule overriding and inheritance and whatnot.
And so we loop over each of the headers, and we grab all the CSS files. We grab the contents from the CSS file and stick it out in the root as one single file, global CSS, IE6, IE7. And we went and rendered the page, and immediately we noticed a big problem on the site. None of the images showed up.
And so we said, oh, what happened here? What went wrong? And to explain this, let me explain a little bit of back history. The online store is 10 years old, right? And that's 10 years of people coming in through the store and having their kind of touch with it, deciding the directory structure and whatnot. And because of that, there are files all over the place. And people wrote the background image URL paths relative to the location of the CSS file.
So when we looped over all the files, pulled all the CSS out, and stuck it in the root, we never changed any of the paths. And so it referred to images that weren't there. And so we decided that we had to go through and rewrite these URLs. And so there was a fair bit of work to do that, but we were able to do that, and that solved our problem.
And so the team and I, we took a step back, and we said, how do we prevent ourselves from being bit by this again? What do we do? And well, it turns out we'd already done the work to grab the image URL. So we thought, why don't we just verify that these images actually exist? So we do that.
We go out and we rewrite the URL and we verify that that image is there. You know, maybe somebody forgot to check it in. And if that happens, it throws us in the air and we know to go look at it. Maybe the path was written wrong. And so we're able to verify that the asset actually exists. And so that worked out great for us. We said, well, what else can we do here? This is a great phase to kind of push any of this processing into.
And we said, well, it makes sense that we want to validate our CSS here. You know, look for syntax errors and things like that. And there's a widely accepted validator by the W3C called Jigsaw. It's at jigsaw.w3c.org. But it's got two big problems for us. The first is that it's a web app.
And so it doesn't work with our command line workflow that we were kind of going with. And the second is because it's a web app, we have to send our code out to it. And I don't know about you guys, but there's no way that we're going to be able to do that. There's no way that we could possibly send our code out to a web app, you know, that's proprietary Apple code.
It would be a matter of, you know, an hour before the blogs are trying to interpret what the various class in the CSS is. So that really wasn't an option for us. And so we opted to write our own validator. You know, it was a pretty simple process. It took a couple hours over a week to kind of get it done. And that's not really the important thing that I want to talk about. What writing a validator allowed us to do was to chain load in other development rules.
And, you know, to kind of explain this and kind of give you an idea of something that you might be able to do with a step like this, we have a lot of people at the online store that, you know, are fixing bugs or fixing issues, and they may not have a global view of all the CSS. And, you know, to their credit, they're trying to get their work done.
And to do that, they might write an ultra-generic rule. They might write something like ULLI background colon red. And then that fixes their issue, right? But when you concatenate all the CSS and you pull it out and then that out site-wide, it may turn all the ULLIs red on the site and cause more problems.
And so what we did is in the validator, we wrote, like, dev rules. And essentially, it's a starting comment that says, you know, all rules from here to a termination comment should begin with a class rule namespace. And so we go through, and when it hits that block, it verifies that all the rules below are actually namespaced.
And if it, you know, it fails that test, we throw an error, and it lets us know that we should probably go look at this before we push out to production. And hopefully we won't push out something that will, you know, cause havoc on the site or, you know, cause havoc on a small piece of the site that you rarely go to.
So on the JavaScript side, we did something very similar. We checked the dependencies, and this allowed us to dynamically generate the list of JavaScript files that we can catenate out into the root.
[Transcript missing]
And so, you know, Jeff mentioned earlier that the code doesn't necessarily need the white space and comments to be readable by the browser. It doesn't need to be human readable to be readable by the browser. You know, we put that in for us. I certainly wouldn't want to work on source files that aren't commented or don't have proper formatting, and I know none of my coworkers would either.
But we can, during this packaging phase, we can strip all that stuff out. And so we elected to use the YUI Compressor by the Yahoo! UI team. It's a great utility. The guys have put a lot of work into it. It's very robust. It works with both CSS and JavaScript, and it works on the command line, so it works with our workflow. And so it's a great tool. It just strips all the comments and white space out and puts everything on a single line. It's the minimal amount of code that the browser needs to render a page.
Another thing that you can do is to rewrite your rules, right? There's this notion of, or some CSS properties have a top, right, bottom, left variant to it, right? Things like margin and padding and border have these top, right, bottom, left components. But you can also write a shorthand version of that rule. It looks something like this, where you have margin and then you pass in the value starting at the top and going clockwise, right? And the difference between the two rules is some number of bytes.
And obviously the shorthand version is smaller. Ultimately, we decided not to go this route because we weren't able to accurately QA our code before it was pushed out to production. And so we didn't feel comfortable pushing out code that we weren't able to QA. But if you can fit this into your workflow and you're able to QA it and you feel comfortable and you need to eek a few more bytes out, this is definitely something to look at.
On the JavaScript side, we did something very similar. Again, like I said, we used the YUI Compressor to remove the whitespace in the comments. And so we kind of compressed the JavaScript up real nice. But we do something a little bit different with the JavaScript. We also shorten the local variables.
Now, you may have heard of this being called obfuscation, right? So there's a notion that, you know, variables can be somewhat long at times, right? You could have, "This is the name of a div." That would be, you know, the variable. That could very easily be the letter A.
And, you know, A is obviously a lot shorter than this is the name of a div. And, you know, so we found that by using obfuscation, which is traditionally used to make your code a lot harder to read, right, we found that by repurposing the tool, we were able to get compression techniques out or compression results out of it. And so we employed it in this case.
So that kind of made up our phase two. And we went back to the baseline, and we tested our phase two against what we'd already done. And we saw that the JavaScript was basically cut in half. We went from 314K of JavaScript down to 157K. And CSS went from 153K down to 128K. And this is great.
It's a step in the right direction, but it's still not quite enough. We thought the sweet spot is really 100K for the CSS and JavaScript combined. That's what we were targeting. We thought that it would really, it wouldn't cost the user a lot of time if they only had to download 100K. So that's what we shot for. And we had to look at other avenues.
What did we look at next? We've already spent a bunch of time on getting rid of all the requests, and we'd already compressed the code down as much as we could. Where else can we eke a bit of performance? And so we said we should probably start looking at the vending mechanism.
We should probably start looking at Apache. And modern browsers, they support gzip compression, right? Very efficient algorithm to kind of compress, take compressed data, decompress it on the client side so you can transfer this compressed data. And so we started taking a look at Apache and how we could use Apache to do this.
And it turns out Apache ships with two modules, ModDeflate and ModGZip. And the way I like to distinguish the two, ModDeflate is really for assets that you want to compress on the fly and then bend, and ModGZip are for assets you want to pre-compress and stick up on the server. Now, at the online store, we have an edge caching solution, as Jeff mentioned. We use Akamai. An open source, a very similar open source caching solution would be like Squid. So it's, you know, something to take a look at.
But because we have that caching solution in front of us, it periodically hits our web servers, and then we use ModDeflate to compress the assets, bend it up to Akamai, and then Akamai handles the job of bending that to the millions of users that hit the online store. So we don't really need to worry about server utilization, and that's why we use ModDeflate.
If you didn't have a caching solution in front of your web servers, you might want to take a look at something like ModGZip. And this would allow you to pre-compress all these assets, you know, probably during your packaging phase, and then stick them up on the server as part of your deployment. And then you can use ModRewrite to kind of redirect all the requests for static assets over to these GZip compressed versions, and then bend those out.
So this was our phase three. So we put this into place, and we pushed this out to the QA server, and we tested it. And we saw that the sizes went through the floor. This had a huge impact for us. And so we went from 314k of JavaScript all the way down to 38k, and on the CSS, we went from 153k at the beginning down to 22k.
If you remember in Phase 1, our request time was 2500 milliseconds. We had gotten that down to 136 milliseconds. This is fantastic. We couldn't have been happier. We overshot our goal. Our goal was 100K. We were able to get the static assets down to 60K. Jeff Watkins, Ryan Orr So what this allowed us to do now is we have room to kind of grow and to add interactivity and to add features and not have to worry about going over our target file size.
Jeff Watkins, Ryan Orr To kind of underline the amount of reduction that we saw from beginning to end, I kind of want to take a look and give you some numbers here. So from the start, from our start file size of the CSS to the end, we saw an 86% reduction with these techniques that I just outlined.
With the JavaScript, we saw an 88% reduction. And the request time latency, we saw a 97% reduction. We couldn't have been happier about this. And really what I want you guys to take away from this is that these are all really easy techniques, right? None of this is rocket science.
None of this is too hard to comprehend. And it's things that can be piecemealed together. You don't have to follow the way we did it. You can take small pieces and put it into your workflow as you see fit. And hopefully, you'll see that. And you'll notice these large gains like we were seeing. And with that, I'd like to hand it back to Jeff Watkins.
Of course, the real challenge now is going to be how we top this, because you know you never get to stand still, right? Now, I see a lot of you have your laptops open, and if you're looking at the source of the store, I have to confess you won't see this yet. But just keep looking. It takes us a little while to roll these things out of QA and into production. So how many of you are responsible for adding animation and interactivity to your sites? Anyone? So we're going to talk about that a little bit.
Once upon a time, you know, animation was sort of the province of marquee tags and blink tags. Don't you miss those? Oddly enough, we just had someone use a marquee tag just recently, purely as an attention-getting thing. Hey, there's content missing here, but boy, did it get my attention.
So we all want to be adding more and more animation to our sites. And the trick is now it's really not just a gimmicky thing. Now we're doing it as much to add context and feedback to our visitors. It's really important for us to provide a sense of how do I get back to that previous state? Well, we recently rolled out a brand new feature on the store, which maybe some of you have encountered already, which was an enhancement to our photo galleries on the product details pages. What we wanted to give our visitors was this rich, immersive experience of being able to zoom in, pan around, explore their products so that, you know, maybe they would be able to make the buying decision.
And we wanted to give our visitors a sense of how to get back to their original state of mind. And we wanted to give our visitors a sense of how to get back to their original state of mind. Because the more we sell, the better off we all are, right? So we built this new feature.
And as you can see here, kind of the standard product details page, the visitors hovering his cursor over one of the thumbnails for the Nike iPod Sport integration kit. But when he goes ahead and clicks on that thumbnail, the page smoothly transforms into a photo gallery. We didn't want to have light boxes popping up. We didn't want to have pop-up windows.
Because we didn't want to get in the way of the experience. And we always wanted to have the option of you just exploring the page, scrolling down, reading the details, seeing how this product -- basically, we wanted to be all about the product rather than our fancy animation. So let's take a look at that again.
And I want to point out some things that you should be looking at. Specifically, the product text just goes away, right? Because we're going to be looking at the product text. So let's take a look at that again. And I want to point out some things that you should be looking at.
Specifically, the product text just goes away, right? Because we're going to be looking at. And I want to point out some things that you should be looking at. Specifically, the product text just goes away, right? Because we're going to be looking at. And I want to point out some things that you should be looking at.
Next, it's always important for the visitors to know, is this a good product or is this maybe not so good a product? So we take the customer rating information, we move that over to the side so it's always visible. So you never get lost and figure, hey, this is not really a great product, but I'll buy it anyway.
And finally, to provide that context, to provide feedback to, here's how you get back to the descriptive portion of the site, we bring in a close box. So the actual button for you to click to close the gallery just sort of fades in. So if you watch carefully, you'll see all of those things happen really smoothly.
And that's not just because this is a quick time animation of it. But what we're striving for here is 50 frames a second. Now, to put that in perspective, traditional film runs at about 24 to 25 frames a second. Television runs at about 30 frames per second. And new high-definition content is targeting about 60 frames a second.
Jeff Watkins, Ryan Orr And the reason for that is it turns out as you add more pixels, your eye gets better and better at detecting motion artifacts. So the more frames that you have per second, the less noticeable those artifacts are. Well, we want to take advantage of that. So we needed to build a really robust way of doing this animation. But at the same time, we had to have it be really performant so that we didn't lag down any other animations that might be occurring on the site at the same time.
And we came up with two optimization techniques that we're going to talk to you about today. There are tons of optimization techniques for JavaScript. You can probably fill an entire session just with JavaScript optimization tricks. But we're going to talk about two. The first being, sometimes it's necessary to leave the library. Now, JavaScript libraries are essential tools.
I don't know really anybody who develops a sophisticated website without a library of any sort. And I'm not, I'm certainly not up here telling you that libraries are terrible. No, they're actually really critical. They allow us to build much better sites in far less time. But they're developed for everyone, right? They're not specifically developed for you.
And as a result, there may be more in that library than you actually need, and that might actually be slowing down your code. We'll take a look at a really good example of that in a moment. And sometimes it helps to be really, really laser focused and specific. And we'll see that that provides us with dramatic speed improvement.
But Let's get to the code. So this is actually the core of that animation. This is the setup function. What we do, and this is a really common technique, is we set up our CSS as an inline, open, and zoomed mode. So we have separate class names for each one of those.
And that way, as the CSS developer is working, it just sets the class, everything pops into place, everything's good, we can make sure everything's located correctly. But what we do is before we apply the class in the animation, we find out all the properties of those nodes, then we apply the class, find out their new properties, and then smoothly animate between them. Okay? Well, when we did this the first time... we encountered 50 nodes. Okay, not too bad. A little bit more nodes than we'd like. You know, we can't always use multiple background images like we do with Safari.
But we execute this twice, and that made our runtime 42 milliseconds. So to put this in perspective, if we're shooting for 50 frames a second, that means each frame is only 20 milliseconds. So we've dropped two frames on the floor. Now, realistically speaking, if we are getting 50 frames a second, you won't notice. But not everybody out there is running Safari on the latest Mac Pro.
There are people out there running it on MacBooks. There are people running Safari on Cubes. And there's even that poor guy out there running it on IE. So it's really, really critical that we do everything we can to speed this up, because that IE guy, he's going to be seeing 200 milliseconds or more.
So he's going to drop way more than two frames. He'll probably just notice it pop into place. So we want to do whatever we can to make that better. But it turns out, there's not a lot of code here. Essentially, it's a tight loop with a call to element get style.
So let's take a look at element get style and see what we can see. Now, I don't mean to be picking on the prototype library, because this is the source code for their get style function. I chose prototype because it is one of the most popular libraries out there, and I figured a lot of you would be very familiar with it.
It turns out, though, that as I mentioned earlier, this is written for everyone, including people who pass in IDs. We're not going to ever pass in an ID. We're always going to pass in an actual node. People who pass in CSS properties rather than the JavaScript equivalent of them, we're only ever going to pass in the JavaScript equivalent. So we can throw away a lot of this code. It's good code, but for our uses, we don't need it. So we're only going to consider just the code that we're interested in.
So we see that we have one access to an inline style, and we do that for performance reasons. If the inline style is there, excellent. We return it right quick. If the value is null, then we call getComputedStyle to find out what the node's actual styles are, and then we access that computed style and return it for a grand total of three operations. Really good. I mean, if the prototype guys could optimize this even more, I guarantee you they already would have. But keep in mind, this is generic code.
It's meant for everyone to use. So if we put this in place in our getStyles function, what we see is not so good performance. In addition to the call to element getStyle, which carries along with it three additional operations, we're doing that for every single property. Well, we animate 30 to 40 properties. So you can easily see that we're doing a tremendous number of operations per node, per loop through those nodes. So it's no wonder to me that it's taking a little bit longer than it should. So this brings us to our first point.
Sometimes it's necessary to leave the library. So what we're going to do is we're going to rewrite this entirely from scratch to do the exact same thing, but do it a lot faster. So we're going to rewrite this entirely from scratch to do the exact same thing, but do it a lot faster.
[Transcript missing]
We arrived at the same result nonetheless. And this really, like I'm telling you, this is not hard stuff. But the end result is a seven times speed improvement. Now, I'll do a fair amount of work to improve anything by seven times. And when it's a critical feature of the site, like the product gallery is, that's pretty awesome. So to recap, We talked about some of the problems that we're all seeing. I'm sure nobody's site loads as quickly as you want it to.
I'm a web developer. Mine could never possibly load as fast as I wanted to. And we use more bandwidth than we'd like. You know, bandwidth is only going to get cheaper. That's true. But you still got to pay the bill. So the less you use, obviously, the lower that bill's going to be. We looked at the strategies that you can all use.
They're really simple. They're the sorts of things that all you got to do is find time in your schedules to apply them. And then we looked at how we're applying them on the online store. And finally, hey, we looked at ways to add even more interactivity to our websites and hopefully provide a richer, more immersive experience to our visitors.
So if you'd like some more information, please feel free to contact Vicki Murley or Mark Merlone. They're the evangelists for Safari and Internet technologies, respectively. And do take a chance to look at the JavaScript Lint tool. It's really tremendous. I can't tell you the number of times it's caused, caught dumb bugs of mine, which would have caused tremendous problems. But probably the best thing it ever will do for you is tell you that you never declared a variable, and you're promoting it then to global scope. So also the YUI Compressor, say that 10 times fast. And this is a great tool.
They've done an enormous amount of work on it, as Ryan mentioned. And it'll really take your code, smoosh it down, and reduce it quite a bit. Jeff Watkins, Ryan Orr If you didn't catch it yesterday, I'd really encourage you to go onto iTunes in a week or so, or maybe more, and download the debugging session. Tim and Adam did an awesome job of walking you through the new debugging features in Safari.