Skip to main content

Command Palette

Search for a command to run...

Why Don’t Websites Put All Their Images Into One Giant JPEG? (Nerd-Sniped by My Brain)

Updated
8 min readView as Markdown
Why Don’t Websites Put All Their Images Into One Giant JPEG? (Nerd-Sniped by My Brain)
D

Hey there!

I'm an electronics engineer who's dabbled in a bit of everything, including full-stack development and web3 technologies. I love building cool stuff and am always looking to connect with other like-minded professionals. When I'm not tinkering with new projects, you can find me scouring the internet for the latest and greatest in tech.

I had a simple question:

Why do websites load lots of individual images instead of stitching them into one giant image and cropping out the pieces they need?

At first glance, an image atlas sounds great.

Instead of this:

image-1.jpg
image-2.jpg
image-3.jpg
...
image-100.jpg

You create this:

site-atlas.jpg

Then each UI tile crops a specific region from the atlas.

That would mean:

  • fewer network requests

  • images arrive together

  • no staggered popping

  • maybe better perceived loading

  • maybe less request overhead

Not a new idea by any means. Games and UI libraries have used sprite sheets and texture atlases forever. The question is: why isn’t this the default for websites?

The experiment

I compared three approaches:

  1. Individual optimized images

    • 14 separate optimized JPG files

    • rendered as normal <img> elements

  2. Canvas atlas

    • one stitched atlas JPG

    • each tile rendered by cropping from the atlas into <canvas>

  3. CSS background atlas

    • one stitched atlas JPG

    • each tile rendered with background-image, background-size, and background-position

The atlas was regenerated from the same optimized images, so the comparison was more fair.

NOTE: I ran the experiment by hosting it locally. so all the number you see are when you have the application served using a python server running locally.

If you want to poke at it yourself, the experiment is live here: https://daviddodda.com/experiments/img-atlas/
note: make sure you disable cache. try each version a couple of times.


What I measured

I focused on three headline metrics.

1. Transferred

How many bytes were downloaded?

2. Network complete

When did the last required image resource finish downloading?

3. Visible / ready

When was the image grid actually ready to see?

This last one matters because network completion is not the full story. The browser still has to decode images, rasterize, paint, composite, and show pixels.


Results

On a remote machine running Chromium, all files hosted locally, 10 runs each:

Approach Transferred Network avg Visible avg Visible median
Individual images 1.6 MB 34.4 ms 292.4 ms 289.1 ms
Canvas atlas 2.3 MB 12.6 ms 454.2 ms 453.7 ms
CSS background atlas 2.3 MB 12.9 ms 222.5 ms 217.5 ms

The surprising result:

The CSS background atlas was the fastest to visible.


The atlas won the network

The atlas had a clear network advantage:

Individual images: ~34 ms network complete
Atlas:             ~13 ms network complete

Well, one larger request has less overhead than many smaller requests.

This effect is especially visible when the server/browser are using less optimal connection behavior. In my test, Chromium reported http/1.0 for the local server, so request overhead was more obvious than it would be under HTTP/2 or HTTP/3.

With modern HTTP/2 and HTTP/3, many individual image requests are less painful because requests can be multiplexed over one connection. But request overhead still exists.


The atlas has dummy pixels

The individual images transferred:

1.6 MB

The regenerated atlas transferred:

2.3 MB

Why?

Because an atlas is a rectangle. Real images have different aspect ratios. When you pack them into one big rectangular sheet, you often create empty space.

In my case:

Individual images total pixels: ~23.8M
Atlas pixels:                   ~31.2M

That is about 31% extra pixel area.

So even though the atlas used one request, it transferred more data and required the browser to decode a bigger image surface.


Canvas atlas sucks

The canvas atlas looked like it should be fast (thought modern hardware was fast enough). It loaded one atlas image, then cropped each tile into a canvas.

But the results were poor:

Canvas atlas visible avg: ~454 ms

The breakdown showed:

Atlas network complete:        ~13 ms
Atlas decode wait after load:  ~167 ms
Canvas crop drawing:           ~6 ms
Paint/composite wait:          ~261 ms

The actual JavaScript canvas drawing was not expensive.

The expensive part was making all those canvas results visible.

That means the bottleneck was not:

ctx.drawImage(...)

It was the browser’s later paint/composite work.


CSS background atlas does not suck

The CSS background atlas used normal DOM elements:

.tile {
  background-image: url("./atlas/animals.optimized.jpg");
  background-size: ...;
  background-position: ...;
}

This was much faster:

CSS atlas visible avg: ~222 ms

The breakdown:

Atlas network complete:        ~13 ms
Atlas decode wait after load:  ~166 ms
Apply CSS background crops:    ~2 ms
Paint/composite wait:          ~36 ms

The decode cost was still there. But paint/composite was dramatically better than the canvas version.

So if you are going to do image atlasing in normal web UI, CSS backgrounds may be much better than drawing many cropped canvases.


When atlases do make sense

They are great for:

  • icons

  • sprites

  • emoji sheets

  • game textures

  • small repeated UI assets

  • known fixed-size tile sets

  • maps or tile-like interfaces

  • cases where all assets are needed immediately

They are less great for:

  • photo galleries

  • blog images

  • user-generated content

  • responsive images

  • content-heavy websites

  • long scrolling pages

  • frequently changing assets

now, don't go getting any ideas about rewriting your website's image pipeline to use image atlas. here are some reason why it's a really bad idea.

Why not use an image atlas?

1. You lose lazy loading

With individual images, the browser can load only what is needed:

<img loading="lazy" src="photo-83.jpg" />

With a giant atlas, loading one image means loading everything in that atlas.

That is great if you need everything immediately.

It is terrible if the user only sees 5% of the images.


2. You lose responsive images

The web has powerful responsive image tools:

<img
  src="small.jpg"
  srcset="small.jpg 480w, medium.jpg 960w, large.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 300px"
/>

The browser can choose the right image for the device, viewport, DPR, and network.

With a giant atlas, this becomes much harder. You may need multiple atlases:

atlas-1x.jpg
atlas-2x.jpg
atlas-mobile.jpg
atlas-desktop.jpg
atlas-avif.avif
atlas-webp.webp

The combinatorial complexity gets ugly quickly.


3. You often transfer extra pixels

Atlases require packing. Packing creates waste.

If the images have different shapes, the atlas may contain a lot of empty or unused area.

Even a good packing algorithm cannot always avoid this.

In my test, the atlas had about 31% more pixel area than the individual images.


4. One changed image invalidates the whole atlas

With individual images:

avatar-123.jpg changed

Only that image needs a new URL/cache entry.

With an atlas:

site-atlas.jpg changed

The whole atlas cache is invalidated.

That is bad for websites where content changes often.


5. Prioritization gets worse

Browsers are good at prioritizing resources.

The hero image can be high priority. Below-the-fold images can be lazy. Tiny thumbnails can wait.

With a giant atlas, everything has one priority.

You cannot easily say:

Load the hero image first,
then visible thumbnails,
then below-the-fold images.

The atlas is all-or-nothing.


6. Memory can be worse

A compressed JPG might be 2 MB on the network, but decoded pixels are much larger.

Decoded RGBA memory is roughly:

width × height × 4 bytes

A large atlas can become a huge decoded surface.

In my first broken atlas attempt, the atlas was:

9744 x 25942

That is around:

~1 GB decoded RGBA

Even if the file downloads quickly, that is a lot for the browser to decode, rasterize, and paint.


7. Accessibility and semantics are worse

An <img> has natural semantics:

<img src="rabbit.jpg" alt="Rabbit in grass" />

A CSS background image is decorative by default. If the image is meaningful content, you need to rebuild semantics with ARIA or hidden text.

That is doable, but it is extra work and easier to get wrong.


8. Browser optimizations favor normal images

Browsers have spent decades optimizing:

<img>
<picture>
srcset
lazy loading
decoding
fetch priority
cache behavior
progressive rendering

If you use an atlas, you bypass some of that machinery and take on more responsibility yourself.

Sometimes that is worth it. Often it is not.


What I learned

Every approach has its niche use case (shocker).

My brain nerd-sniped me into exploring and writing about this. It was fun seeing the cute animals load in though.