Skip to main content

Improve perceived page load times by displaying a blurry low resolution image(s) immediately, without any extra HTTP requests. Once page has fully loaded, fetch the high resolution image(s), then cross-fade onto the display.

- (incept: )

Blurred background late loader

A work in progress.

Blah CSS, blah vanilla JavaScript, blah small script.

Mark up

Language HTML
<div class="lateload-container">

  <div class="lateload-img lateload-img-1" data-lateload-src="high-resolution.jpg">

  <div class="lateload-copy">
    Overlaid copy



Either the container or the content copy can define the height.

Language CSS
.lateload-container {
  position: relative;
  overflow: hidden;
  min-height: 400px;

  display: flex;
  align-items: center;
  justify-content: center;
.lateload-copy {
  padding:4rem 1rem

Three superimposed image layers are used.

Language CSS
.lateload-img,              /* bottom layer */
.lateload-img div,          /* middle layer */
.lateload-img div::before { /* top layer */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-position: center;
  background-size: cover;
  z-index: -1;

Both the top and middle image layers have a small, extremely low quality, dataURI encoded background-image defined in the style sheet.

The smaller the image, the better. Keeping below 2 KB is strongly suggested.

Language CSS
.lateload-img-1 div,
.lateload-img-1 div::before {
  background-image: url(data:image/jpeg;base64,…);

To offset the low quality the top image layer has a blur filter applied.

Language CSS
.lateload-img div::before {
  content: "";
  filter: blur(10px);

The middle layer remains as is. It's purpose is to keep the container edges sharp.

The bottom layer, .hero-bg is where the JavaScript adds the high resolution background-image.


Requires the browser to support: addEventListener, requestAnimationFrame, getElementsByClassName and let-for loop which are not tested before use in the module.

Language JavaScript
var LateLoadDataSrcImages = (function() {
  "use strict";
  var lateloadClass = "lateload-img",
      lateloadDataAttr = "data-lateload-src";
Language JavaScript
  var _fadeOut = function(el) { = 1;

    (function _fade() {
      if (( -= 0.05) < 0) {

        // Tidy up and remove from DOM
      } else {

Language JavaScript
  var _displayImage = function(img) {
    var obj = img.divObj;
    window.requestAnimationFrame(function() {
      var childDivs = obj.getElementsByTagName("div");

      // Add image to the bottom layer.
      // It's unseen, behind the top & middle layer.
      // Hopefully no paint or reflow occurs but check. = "url(" + img.src + ")";

      // Fade out the middle layer.
      // It'll take the top (pseudo) layer with it.
      if (childDivs) {

        // Small delay to emulate a poor connection.
        // Remove in production.
        }, 1500);
Language JavaScript
  var _requestImage = function (obj, src) {
    var img = new Image();

    img.divObj = obj;
    img.addEventListener("load", function() {
    }, false);
    img.src = src;

Language JavaScript
  var _initialise = function () {

    // tidy up - don't needlessly clutter DOM or Events.
    window.removeEventListener("load", _initialise);

    let lateLoads = document.getElementsByClassName(lateloadClass);

    for (let lateLoadObj of lateLoads) {
      let dataAttr = lateLoadObj.getAttribute(lateloadDataAttr);
      if (dataAttr) {
        _requestImage(lateLoadObj, dataAttr);
Language JavaScript

  // Test the required features are supported
  // before initialising here:
  window.addEventListener("load", _initialise, false);


No scripting available?

Use CSS instead, not ideal but adequate under most circumstances.

Add the image to bottom layer after a small delay, to allow the image to fully download first.

Language CSS
.noJS .hero_bg {
  background-image: url(high-resolution.jpg);

The use an animation to fade out the blur layers.

Language CSS
.noJS .hero_bg div {
  animation: fadeOut 1s ease-out 3s forwards;

@keyframes fadeOut {
  to {
    opacity: 0

Google compiled

333 bytes gzipped (523 bytes uncompressed).

Language JavaScript
// The compiler transcoded the let-for loop
// Replace with standard for loop.