Turtles all the way down

A presentation at HalfStack on the Thames in September 2022 in London, UK by Niels Leenheer

Slide 1

Slide 1

It is great to be here in London again. I just love the talks so far. Halfstack is just a little bit di erent than most other conferences. There is room for some topics you wouldn’t see anywhere else. And I love that.

Slide 2

Slide 2

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.

Slide 3

Slide 3

So, the first thing I asked myself, is where do I start. So I decided to take the scientic approach and do a comparison.

Slide 4

Slide 4

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.

Slide 5

Slide 5

So we’ve scientifically established that websites that make more requests are slower.

Slide 6

Slide 6

And we can also tell that images require requests to fetch them from the server. No matter how well optimised the size of your images are, they still require a request.

Slide 7

Slide 7

Combine these two premises and we have a conclusion that by inlining images we can make a website faster.

Slide 8

Slide 8

Now back to our website, you’ll notice there is definitely room for improvement.

Slide 9

Slide 9

There is one obvious candidate for optimisation.

Slide 10

Slide 10

Our logo. The space it takes is 240 by 150 pixels.

Slide 11

Slide 11

But nowadays we have high resolution screens. So we need at least a 2x image for the logo to look good. Maybe even 3x, but let’s do fidelity capping, just like Andrea and go for 2x. This logo requires a request. And on every page. So let’s try to inline that image so we avoid that request.

Slide 12

Slide 12

But how do we do this…

Slide 13

Slide 13

Well, an image is just a raster or grid of coloured pixels.

Slide 14

Slide 14

And tables are kind of like grids…

Slide 15

Slide 15

So we can use tables for inlining images. Obviously.

Slide 16

Slide 16

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…..

Slide 17

Slide 17

Eh images? For every single color we’re going to generate a 1 by 1 pixel spacer image.

Slide 18

Slide 18

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.

Slide 19

Slide 19

So… lets just use a background color and then a transparent spacer image. Times 144.000.

Slide 20

Slide 20

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.

Slide 21

Slide 21

Data URLs for the spacer images.

Slide 22

Slide 22

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!

Slide 23

Slide 23

Well… we could just use HTML4 attributes like width and height to replace our spacer image. HTML4 is just better than HTML5 that way.

Slide 24

Slide 24

So now 6.3 MB and down to 3 sec.

Slide 25

Slide 25

But 3 seconds is quite long. So how can we optimise the rendering?

Slide 26

Slide 26

Just think of web workers. Parallelisation improves performance.

Slide 27

Slide 27

And frames are kind of rendered independently from each other.

Slide 28

Slide 28

So let’s inline images using frames instead.

Slide 29

Slide 29

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.

Slide 30

Slide 30

An iframe that shows a document with frame sets and 144.000 frames. And HTML documents for each individual color. What can go wrong?

Slide 31

Slide 31

Yeah, lessons learned from spacer images. Let’s just inline those documents. × 144.000

Slide 32

Slide 32

And apparently there is a 1.000 frame limit… we need 144.000.

Slide 33

Slide 33

So let’s try a different approach. SVG is easily embeddable. So in theory this should be perfect.

Slide 34

Slide 34

So every pixels is basically a small rectangle that we position using x and y attributes.

Slide 35

Slide 35

Brilliant. Just 2 seconds. This is way better than tables. Tables are shit. Don’t use tables.

Slide 36

Slide 36

So let’s try a different approach. CSS. You can even recreate paintings with just CSS, so our logo should be easy.

Slide 37

Slide 37

Absolute positioning. A div with an id for each pixel. And using CSS to set the position and the color.

Slide 38

Slide 38

Larger size as SVG and slower… Hmmm….

Slide 39

Slide 39

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.

Slide 40

Slide 40

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….

Slide 41

Slide 41

But now we have CSS grid. So let’s try that. And instead of Id’s use the nth-child pseudo selector.

Slide 42

Slide 42

Eh…. oops. Apparently nth-child is not really optimised.

Slide 43

Slide 43

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. If I remember correctly from Andrea’s talk, that is a bad score for Largest Contentful Paint on the Core Web Vitals. Grid is slower than absolute positioning.

Slide 44

Slide 44

But what about box shadow? So if you have a 1 by 1 pixel element and give it a background color.

Slide 45

Slide 45

You can give it a shadow. And position it right next to the original element.

Slide 46

Slide 46

But you can add multiple shadows on the same element. And you can position each individually and give each it’s own color…

Slide 47

Slide 47

Do you see where I am going with this…

Slide 48

Slide 48

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 second. So this is. Box Shadows!

Slide 49

Slide 49

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.

40 ms!

Slide 50

Slide 50

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.

Slide 51

Slide 51

So what are the key takeaways from this talk. What are the things that you can use in your projects next week.

Slide 52

Slide 52

HTML4 is better than HTML5.

Slide 53

Slide 53

Use divs for everything except when you can use shadows, of course.

Slide 54

Slide 54

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.

Slide 55

Slide 55

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.

Slide 56

Slide 56

And don’t use 144.000 frames in one frameset. Split them up over 144 framesets of a 1000 frames each.

Slide 57

Slide 57

There is just one issue. And I must admit that I am slightly annoyed. Dylan changed the logo on the website, so that means I can completely start over.

Slide 58

Slide 58

But luckily I have plenty more ideas using a modern web image format called B-M-P. And since browsers usually don’t support it, I can decode it using WebAssembly. I found some C source code for that on some website called SourceForge… And then probably use CSS paint worklets. Or maybe WebGL. Depends on which is faster.

Slide 59

Slide 59

And maybe we can optimise the text of the website by using pixel fonts like Jo used but not with LEDs, but using box shadows.

Slide 60

Slide 60

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.