Progressive Background-Image With Ease

Everyone likes smooth loading. Lately I tried to make the background-image of the menu to load progressively(also to the cover if you view the blog on mobile).

If you take a look at how Medium does progressive image loading(or check out this article), you'll notice that they use JavaScript and canvas to blur and store thumbnails. It seems a bit overkill to me. And when you need to do it on a background-image with background-position, things become even more complicated.

So I needed to figure out a simpler solution. Here's what I came up with:

Blur by default

As the article above mentioned:

By default, when a browser renders a small image scaled up, it applies a light blur effect to smooth the artefacts of the image. The effect can also be turned offfor images like QR codes.

But the default blur effect still feels blocky.

thumbnail

To make it smoother, I applied blur effect to the thumbnails while generating them, using GraphicsMagick for node:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const gm = require('gm')
gm(coverPath)
.resize(30) // or .resize(null, 30) for portrait
.blur(5)
.noProfile()
.write(thumbnailPath, function (err) {
if (err) { console.warn(err) }
// ...
})
// or to base64
gm(coverPath)
.resize(30) // or .resize(null, 30) for portrait
.blur(5)
.noProfile()
.toBuffer(function (err, buffer) {
if (err) { console.warn(err) }
var base64 = 'data:image/' + ext + ';base64,' + buffer.toString('base64')
// ...
})

thumbnail

This looks acceptable to me. No need for a canvas or blur function. Already felt relieved! 😄

Layers

This method divides a component into four layers: container, thumbnail, mask and content.

  • Container holds the full-size background image.
  • Thumbnail holds the blur thumbnail, as background-image.
  • Mask is a translucent black element, to darken the background.
  • Everything else lives in the content layer.

Use z-index to separate the layers.

Image onload

When full-size image is loaded, hide the thumbnail. You can use this technic(with jQuery/Zepto):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var $container = $('.container')
// Matchs the "url(...)"
var bigBgSrc = $container.css('background-image').match(/url\((.+?)\)/i)
if (bigBgSrc) {
// Removes quotations
bigBgSrc = bigBgSrc[1].replace(/'|"/g, '')
$('<img>')
.on('load', function () {
$container.find('.thumbnail')
.addClass('thumbnail--hide') // Hides thumbnail
})
.prop('src', bigBgSrc)
}

A live example:

No-js

No-js support is extremely easy. Just hide the thumbnail.

1
2
3
html.no-js .thumbnail {
display: none !important;
}

For my blog I also made a Sass Mixin/Extend with this method.

您还在局域网。 ——来自隔墙相望的评论