Rent-a-founder

Textured Gradients in Pure CSS

We’re going to create a background like this in pure CSS:

Screenshot of result

Of course, the simple version would be to simply create a CSS gradient (and there are tools that make creating them easy):

Simple CSS gradient

But it just doesn’t look that great, simply because we can only create linear or radial gradients. We want more randomness, something that looks less geometric and predictable.

A common method is to choose a real-life photo and blur it. TinEye offers a free online tool that lets us search for CC Flickr images based on the colours they contain. I added some colours to my search and ended up choosing this picture by Flickr user “whatapar” (under the CC 2.0 license):

Original photo

We don’t need most of the information in this photo so let’s scale it down to a mere 8x6 pixels:

Downsized version

TinyPNG turns this image into 326 bytes and we can use a simple online tool to turn it into a data URI so we can include it directly in our CSS:

body {
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAilBMVEWVsF28hjlmaLKik6tscKNqZKOZr51fZ5Rxc5GjuI9WTYJnaIBsfXBVb26IpWKMlFdlglVndlV1hlRna1Rue1E5SlH/q0uJc0rimEhxdkBXbj/LhDtahzqjgjpcczp9nTnvmzlZWTNYZS85ailmUChLXSYpdSICOSEYIiFMaB4yURAmHApOSgAVDwB+JAIrAAAALnRSTlP+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+iNdOOwAAAD1JREFUCNcFwYMBADEABLDr27ZZd//1moDJ0ItSp8e31H4QJxyTyNwC2mAnbX7iVriGmR5N+YCs4/ZW3W8BZWUEDXOg7OsAAAAASUVORK5CYII=");
    background-repeat: no-repeat;
    background-size: cover;
}

It would be great if that was all there’s to it. And indeed, in Chrome, it looks quite nice:

Blurry image in Chrome

But Firefox does not blur the scaled up image, at least not much:

Blurry image in Chrome

One could add a separate <div> element for the background and apply a filter: blur(10px) to it. But I’m a bit of a purist and adding extra div’s to achieve a purely visual effect is something I try to avoid.

Most modern browsers today support SVG files and the SVG specification includes a Gaussian blur filter (which, in fact, looks even better than the primitive interpolation by Chrome seen above). Let’s create an SVG file with our 8x6 image:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink" viewBox="1 1 6 4">
  <image filter="url(#b)" width="8" height="6" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAilBMVEWVsF28hjlmaLKik6tscKNqZKOZr51fZ5Rxc5GjuI9WTYJnaIBsfXBVb26IpWKMlFdlglVndlV1hlRna1Rue1E5SlH/q0uJc0rimEhxdkBXbj/LhDtahzqjgjpcczp9nTnvmzlZWTNYZS85ailmUChLXSYpdSICOSEYIiFMaB4yURAmHApOSgAVDwB+JAIrAAAALnRSTlP+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+iNdOOwAAAD1JREFUCNcFwYMBADEABLDr27ZZd//1moDJ0ItSp8e31H4QJxyTyNwC2mAnbX7iVriGmR5N+YCs4/ZW3W8BZWUEDXOg7OsAAAAASUVORK5CYII="/>
  <filter id="b"><feGaussianBlur stdDeviation=".5" /></filter>
</svg>

This is what it looks like:

SVG with Gaussian filter applied to resized photo

You may have noticed the viewBox="1 1 6 4" which crops the SVG’s area by one pixel on each side. This is because the Gaussian filter assumes that everything outside the image is white and therefore introduces a white haze at the borders. Cropping the borders somewhat makes it disappear.

Now we turn this SVG file into a data URI once more and include it in our CSS:

body {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSAiaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMSAxIDYgNCI+PGltYWdlIGZpbHRlcj0idXJsKCNiKSIgd2lkdGg9IjgiIGhlaWdodD0iNiIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFBZ0FBQUFHQ0FNQUFBREoyeS9KQUFBQWlsQk1WRVdWc0YyOGhqbG1hTEtpazZ0c2NLTnFaS09acjUxZlo1UnhjNUdqdUk5V1RZSm5hSUJzZlhCVmIyNklwV0tNbEZkbGdsVm5kbFYxaGxSbmExUnVlMUU1U2xIL3EwdUpjMHJpbUVoeGRrQlhiai9MaER0YWh6cWpnanBjY3pwOW5UbnZtemxaV1ROWVpTODVhaWxtVUNoTFhTWXBkU0lDT1NFWUlpRk1hQjR5VVJBbUhBcE9TZ0FWRHdCK0pBSXJBQUFBTG5SU1RsUCsvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NytpTmRPT3dBQUFEMUpSRUZVQ05jRndZTUJBREVBQkxEcjI3WlpkLy8xbW9ESjBJdFNwOGUzMUg0UUp4eVR5TndDMm1BbmJYN2lWcmlHbVI1TitZQ3M0L1pXM1c4QlpXVUVEWE9nN09zQUFBQUFTVVZPUks1Q1lJST0iLz48ZmlsdGVyIGlkPSJiIj48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIuNSIgLz48L2ZpbHRlcj48L3N2Zz4=");
  background-repeat: no-repeat;
  background-size: cover;
}

Looks good, even in Firefox:

Firefox with SVG

One thing I like to do is give the background a bit of a texture. Again, using SVG here (although a PNG may work well here, too):

<svg width="3" height="3" viewBox="0 0 3 3"
  xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink">
  <rect x="0" y="0" width="1" height="1" style="fill:rgba(0,0,0,.2)"/>
</svg>

It’s a one black, slightly transparent bit pixel at (1,1). Once more, we data-URI it and repeat it over the gradient:

html {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSAiaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMSAxIDYgNCI+PGltYWdlIGZpbHRlcj0idXJsKCNiKSIgd2lkdGg9IjgiIGhlaWdodD0iNiIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFBZ0FBQUFHQ0FNQUFBREoyeS9KQUFBQWlsQk1WRVdWc0YyOGhqbG1hTEtpazZ0c2NLTnFaS09acjUxZlo1UnhjNUdqdUk5V1RZSm5hSUJzZlhCVmIyNklwV0tNbEZkbGdsVm5kbFYxaGxSbmExUnVlMUU1U2xIL3EwdUpjMHJpbUVoeGRrQlhiai9MaER0YWh6cWpnanBjY3pwOW5UbnZtemxaV1ROWVpTODVhaWxtVUNoTFhTWXBkU0lDT1NFWUlpRk1hQjR5VVJBbUhBcE9TZ0FWRHdCK0pBSXJBQUFBTG5SU1RsUCsvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NysvdjcrL3Y3Ky92NytpTmRPT3dBQUFEMUpSRUZVQ05jRndZTUJBREVBQkxEcjI3WlpkLy8xbW9ESjBJdFNwOGUzMUg0UUp4eVR5TndDMm1BbmJYN2lWcmlHbVI1TitZQ3M0L1pXM1c4QlpXVUVEWE9nN09zQUFBQUFTVVZPUks1Q1lJST0iLz48ZmlsdGVyIGlkPSJiIj48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIuNSIgLz48L2ZpbHRlcj48L3N2Zz4=");
  background-repeat: no-repeat;
  background-size: cover;
}

body {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMyIgaGVpZ2h0PSIzIiB2aWV3Qm94PSIwIDAgMyAzIg0KICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciDQogIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBzdHlsZT0iZmlsbDpyZ2JhKDAsMCwwLC4yKSIvPjwvc3ZnPg==");
  margin: 0;
}

Here we have the final product, achieved with 1,440 bytes of CSS (uncompressed):

The result

To see the texture better, here’s an original scale screenshot:

The texture

I noticed that some browsers seem to take quite some time rendering the gradient. I assume it’s because scaling the SVG up to page size also increases the size of the Gaussian filter. So at the moment, likely until we have more GPU-based rendering of websites, there is a tradeoff to make: This solution minimizes network traffic but page render times are somewhat slow. To speed up the page render time, it’s probably better to load a gradient image.