A presentation at HalfStack at the Beach in in Newquay, UK by Niels Leenheer
Turtles all the way down
It is great to be here in Newquay. I just love the talks so far. Halfstack is just a little bit different than most other conferences. There is room for some topics you wouldn’t see anywhere else. And I love that.
But now something serious. Dylan, the creator of HalfStack started a new company and that means he is very very busy. So I decided to help him out and optimise the Halfstack website for him. Free of charge.
So, the first thing I asked myself, is where do I start. So I decided to take the scientific approach and do a comparison
Of course you need a good large, representative data set to get significant results. But I did not have that much time, so I just picked two. My own website and some random other website. And you can plainly see that that website is clearly a lot slower. 80 times slower. And it has more than a 1000 requests compared to my 11. Lots of trackers and lots of images.
So we’ve scientifically established that websites that make more requests are slower.
And we can also tell that external images require requests to fetch them from the server.
Combine these two premises and we have a conclusion that by inlining images we can make a website faster.
Now back to our website, you’ll notice there is definitely room for improvement.
There is one obvious candidate for optimisation.
Our logo. The space it takes is 240 by 150 pixels.
But nowadays we have high resolution screens. So we need at least a 2x image for the logo to look good. This logo requires a request. And on every page. So let’s try to inline that image so we avoid that request.
But how do we do this…
Well, an image is just a raster or grid of coloured pixels.
And tables are kind of like grids…
So we can use tables for inlining images. Obviously.
So, a table with a cell for each pixel. Almost 150 thousand cells. And because we don’t want to mix styles with content we are going to use…..
So, a table with a cell for each pixel. Almost 150 thousand cells. And because we don’t want to mix styles with content we are going to use….. Eh images? For every single color we’re going to generate a 1 by 1 pixel spacer image.
That is 16.7 million image files x 88 bytes = 1.35 GB. But about 80% along the way of generating these images something in the filesystem broke. Apple claims you can put 2.1 billion files in a directory. But that is not true. Your Mac will crash. It will fail to boot and you need to start it up in rescue mode do disk repair and delete the whole directory from the Terminal before you can use your computer again.
So… lets just use a background color and then a transparent spacer image. Times 144.000.
And it works….. The HTML is just 7.1 MB and it “only” took 9 seconds to render. But still 1 request. So we can do better.
Data URLs for the spacer images.
Of course that means our HTML will be bigger. And rendering times will go up too. But now we have zero requests…. We are done!
Well… we could just use HTML4 attributes like width and height to replace our spacer image. HTML4 is just better than HTML5 that way.
So now 6.3 MB and down to 3 sec.
But 3 seconds is quite long. So how can we optimise the rendering?
Just think of web workers. Parallelisation improves performance.
And frames are kind of rendered independently from each other.
So let’s inline images using frames instead.
Problem one. There is no documentation. Not in the spec. Not on MDN. So I headed over to the trusty W3schools and yup. All the info I needed.
An iframe that shows a document with frame sets and 144.000 frames. And HTML documents for each individual color. What can go wrong?
Yeah, lessons learned from spacer images. Let’s just inline those documents.
And apparently there is a 1.000 frame limit… we need 144.000.
So let’s try a different approach. SVG is easily embeddable. So in theory this should be perfect.
So every pixels is basically a small rectangle that we position using x and y attributes.
Brilliant. Just 2 seconds. This is way better than tables. Tables are shit. Don’t use tables.
So let’s try a different approach. CSS. You can even recreate paintings with just CSS, so our logo should be easy.
Absolute positioning. A div with an id for each pixel. And using CSS to set the position and the color.
Larger size as SVG and slower… Hmmm….
So let’s try pseudo selectors. First pixel is a single ::after. Second is two times :after. Third pixels is :after:after:after. And so on.
Well, that didn’t work. I actually ran into an issue with generating. The file is 37 GB big. And even though the maximum string length in Javascript is 8192 Terabytes, V8 can’t handle strings that long. The positive thing is, gzipped it is only 62 MB….
But now we have CSS grid. So let’s try that. And instead of Id’s use the nth-child pseudo selector.
Eh…. oops. Apparently nth-child is not really optimised.
After 21 minutes it stopped rendering. Not sure what the problem is with the rendering, but without nth-child and back to Id’s it still took 59 sec but at least it looks correct. Grid is slower than absolute positioning.
But what about box shadow? So if you have a 1 by 1 pixel element and give it a background color.
You can give it a shadow. And position it right next to the original element.
But you can add multiple shadows on the same element. And you can position each individually and give each it’s own color…
Do you see where I am going with this…
So this box-shadow CSS property is 3 MB big. It has 139.999 shadows in one declaration. But that is only 380 KB gzipped and rendering took only 0.1. So this is. Box Shadows!
But we have a lot of dark blue here. What if we don’t include that in the shadows itself, but just set a background color on its parent.
We’ve gone from 18MB to 604KB and from 13 seconds to 40 ms. A big improvement. Success! And while 604 KB still sounds a lot. It is still less than the average size of a banner ad on the website of the Express. And I kid you not, they spend more time waiting for the server to respond than we spend to render the image.
So what are the key takeaways from this talk. What are the things that you can use in your projects next week.
HTML4 is better than HTML5.
Use divs for everything except when you can use shadows, of course.
The maximum length of a string is nine quadrillion , seven trillion , one hundred ninety nine billion , two hundred fifty four million , seven hundred forty thousand , nine hundred ninety one bytes.
And w3school is much more complete than mdn. Mdn is good for that fancy new stuff, but if you want the good stuff like frames, blink and marquee, you’ll need w3schools.
And don’t use 144.000 frames in one frameset. Split them up over 144 framesets of a 1000 frames each.
I must admit that I am slightly annoyed that apparently they changed to logo, so that means I can completely start over. So I hope you learned some useful information here. Learned some new skills. And with that I just want to say thank you and let’s make inlining images a top priority.
Every image you use on your website is a request to the server. And I think somebody once told me that we should use as few requests as possible. Yes, that sounds about right. So what if we could embed images right in your HTML? That would make our website load at least twice as fast and reduce requests by… five. Probably. Okay, we can already do that with DataURLs. But what if we couldn’t?