LazyLoad Images in Simple Steps for Casper Theme on Ghost

LazyLoad is a concept to defer the loading of assets until they are needed. Let's implement simple LazyLoad for Images & make Casper for Ghost do lazyload.

LazyLoad Images in Simple Steps for Casper Theme on Ghost

simple-lazyload-in-casper-ghost
LazyLoad is a concept where we defer the loading of the assets until they are needed; generally Images.

In this article we will see how to defer the loading of the Images and how to make Casper theme on Ghost blog do the lazyload.

In simple steps; we need to do following to build lazyload for image:

  • build/find a small size image which act as placeholder and loading indicator
  • show placeholder for image and store the image info in other way like attribute or data-*
  • Now the JS code will evaluate whether the picture is going to be in front of user
    • If yes; replace the image from attribute to main src or whichever way it is needed
    • If No, let it be like as it was

So lets see things step by step for a img tag which will be lazy loaded.

Step 1: Placeholder

let's consider following image for placeholder:

And the img tag we will be using is:

<img 
  alt=''
  src="https://images.unsplash.com/photo-1517232875856-75e69324a4f9?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ&s=d5655cd81179d3df9288b7993f300358" />

Let pit the placeholder on it

<img src="https://via.placeholder.com/400x250?text=Loading..." class="lazyload" />

Step 2: Image Info

Let's add the correct image info with data-src attribute

<img
  class="lazy" 
  src="https://via.placeholder.com/400x250?text=Loading..."
  data-src="https://images.unsplash.com/photo-1517232875856-75e69324a4f9?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ&s=d5655cd81179d3df9288b7993f300358" />

Step 3: Check if img is in viewport

To check if image is in the viewport, we will need to watch the scroll movements.

It can be done by attaching the scroll event listener which will evaluate the position.

function onScroll() {
    // check the positions here
}

window.addEventListener('scroll', onScroll, {passive: true});

Now in our onScroll function, we will collect all the lazyload elements i.e. img tags with class lazy

document.querySelectorAll('img.lazy');

Anf now we will loop over the collection and see their position. The position of an element is obtained by the DOM function getBoundingClientRect() in the Element. This function returns DOMRect object which has following structure:

DOMRect

The important property for our current use case is top and bottom

And we need the window's height to know our visual anchor point. We will have our calculation point with window.innerHeight

Now we can determine if the element is entering from bottom by following condition:

var pos = el.getBoundingClientRect();
if (pos.top <= window.innerHeight) {
   // 
}

But this element can be way above and not actually be inside viewport; we can check that by adding following condition with top to be non-negative

if (pos.top <= window.innerHeight && pos.top > 0) { }

And similarly if the element enters from top:

var pos = el.getBoundingClientRect();
if (pos.bottom >= 0 && pos.bottom <= window.innerHeight) { }

And combining both of them; it looks like as follows:

function inViewport(pos) {
  return (pos.top <= window.innerHeight && pos.top > 0)
    || (pos.bottom >= 0 && pos.bottom <= window.innerHeight);
}

And our onScroll function can go like this:

function onScroll() {
  var lazyEls = document.querySelectorAll('img.lazy');
  for (var i = 0; i < laxyEls.length; i++) {
    var el = lazyEls[i];
    var pos = el.getBoundingClientRect();
    if (inViewport(pos)) {
      //
    }
  }
}

Step 4: Load image if needed

Loading image is quite simple part, we just need to update the src and remove the lazy class from the element; which can be done as follows

function lazyLoad(el) {
  el.src = el.getAttribute('data-src');
  el.classList.remove('lazy');
}

And putting it inside the onScroll it will go like:

function onScroll() {
  var lazyEls = document.querySelectorAll('img.lazy');
  for (var i = 0; i < laxyEls.length; i++) {
    var el = lazyEls[i];
    var pos = el.getBoundingClientRect();
    if (inViewport(pos)) {
      lasyLoad(el);
    }
  }
}

LazyLoad in Casper for Ghost

In casper, the main concept stays same; thought the approach changes a little bit because the images are added as background-image to div rather than src to the img

In casper, for the homepage, the element we are looking for is having the class post-card-image and on the single post page, the element is with class post-full-image.

These elements will have the featured image set as the background-image. So we update the src in template as:

{{#if feature_image}}
  <a class="post-card-image-link" href="{{url}}">
    <div class="post-card-image lazy" data-src="{{feature_image}}"></div>
  </a>
{{/if}}

And the lazy class will have out placeholder as the background image.

And our lazy collection and the lazyLoad function will update as follows:

document.querySelector('.lazy');
function lazyLoad(el) {
  el.style.backgroundImage = 'url(' + el.getAttribute('data-src') + ')';
  el.classList.remove('lazy');
}

Rest remains same and this will enable the lazyLoad on the featured images of your casper theme.

If you are more curious about how it is done on this site Time to Hack; you can check it out here: https://github.com/time2hack/casper/blob/t2h/index.hbs


Conclusion

LazyLoad will definitely improve the sitespeed. What do you think about it? Let me know through comments ? or on twitter at @heypankaj_ and @time2hack.

If you agree, share this article with others ?