120kb less

120kb less

• Reading time: 5 minutes

After spending quite some time rebuilding my website from the ground up and trying to spare users from downloading every useless byte, I found out that I was forcing them to download 120kb just to display a really discreet grain effect over the background.

If planning and orchestrating a full optimization pass on a system or a website seems easy, preventing long-term degradation is another matter entirely.

As I'm not planning on building an observability system for my personal website, I'll have to be careful with asset additions and changes like this one.

The culprit #

Once I was done with every tweak I could think of to keep the payload as light as possible, I introduced, on second thought, a film-like grain effect over the background of my website. It was a simple .png file of 250px by 250px, and I didn't think much of its size. On top of that, it was converted and served either as a webp or an avif depending on the browser capabilities.

Taking my homepage as an example, with this new grain.avif image, I was going from these stats:

35 requests
27.4 kB transferred
66.1 kB resources
Finish: 10.95s
DOMContentLoaded: 4.71 s
Load: 8.91 s

To these:

36 requests
147 kB transferred
185 kB resources
Finish: 11.33 s
DOMContentLoaded: 4.67 s
Load: 9.26 s

Stats recorded with a 3G throttled connection.

The impact on loading time is anecdotal, but multiplying the transferred size by more than 5 feels like chopping my optimization work with an axe.

A simple fix #

At first, I was tempted to simply remove this effect, thus immediately resolving the issue. Except I'm quite fond of the texture it adds to my otherwise bland background.

Then I searched for an alternative way to create a noisy grain effect that would be lighter and found it in the SVG specification.

The following simple code was the answer:

<svg viewBox='0 0 110 110' xmlns='http://www.w3.org/2000/svg'>
  <filter id='grain'>
    <feTurbulence
      type='fractalNoise'
      baseFrequency='0.55'
      numOctaves='3'
      stitchTiles='stitch' />
  </filter>
  <rect width='100%' height='100%' filter='url(#grain)' />
</svg>

This SVG file weighs only 292b: more than 119kb less than the previous image.

The result #

Here, side by side, are the old and new noisy grain effects:

It is clearly not the exact same grain when shown on its own like that, but when used as an overlay, the effect is pretty much the same using this bit of CSS and adjusting the opacity:

.grain {
  position: absolute;
  inset: 0;
  background: repeat center/110px url("/public/images/grain.svg");
  border-radius: 0;
  opacity: 20%;
  pointer-events: none;
}

opacity needs to be adjusted: on a light background you may need to set it higher than on a dark one. mix-blend-mode can also be used in order to improve the grain integration depending on the color of your background.

Bonus: a live noise grain generator #

Below, you'll find a small web component I built in order to generate your own grain effect SVG file with custom parameters. You can adjust the baseFrequency and numOctaves of the grain effect. Opacity can also be adjusted, and text can be toggled for preview purposes.

See MDN documentation if you are interested in understanding the different parameters available for the <feTurbulence> SVG element.

As I keep moving things around on my website, it seems necessary to take things slow and reflect on every change if I want to keep things clean.