Hawk's By Design

Have you ever just had the simple thought of: I wish these images waited to load.

Well, that’s called lazy loading, if you didn’t know. It’s a popular technique for sites, especially image heavy ones, to delay the loading of images until they are actually needed. Why load an image down the page when it’s not necessary at the beginning? Free up that bandwidth!

Every time I think about lazy loading, I go looking for a library. There are some good ones out there, but then I always think to myself, why use a library? I’m not a big fan of importing a library for simple things. Heck, I don’t even like using jQuery anymore.

With that in mind, I’m going to run through a quick and easy way to lazy load images usually vanilla javascript.

This is a bare bones run through, there are obviously going to be places for improvement

Here are the steps:

  1. Figure out your HTML structure
  2. Add event listener for scroll
  3. Check if image is in the viewport
  4. Load image asynchronously
  5. Add image to the page

Simple, right?

HTML Structure

This may seem simple, but how you want to structure your HTML is a big deal. You could just want to have a normal image tag, or maybe you have an elaborate div setup.

For simplicity, we are going to use the following markup.

<div class="image-container">
    <noscript>
        <img src="" alt="" />
    </noscript>
</div>

What is <noscript>? Well, it’s a handy tag that only shows what’s inside it whenever javascript is disabled. At this point in the web, usually everyone has javascript enabled, but it’s better to be safe than sorry.

This <noscript> tag also prevents the image from loading before our javascript executes. It does this because if javascript is enabled, then everything within this tag is seen as a string, not html.

So if we don’t have javascript, we still get our image load, but with javascript, it won’t load anything on the UI and won’t make a network request for the image.

Yay! Progressive enhancement!

Scroll Event Listener

No matter how you look at it, you are going to have to have some sort of trigger. The best trigger, in my opinion, is the scroll event. That way, we can run a check to see where the user is on the page, and load accurate images.

var scrollTimeout;
document.addEventListener('scroll', function() {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(function() {
        // Loop through images
    }, 250);
});

Now the scroll event is something that fires a ton with every scroll. We aren’t going to want our code to execute that many times; bad for performance. So a neat, clean trick is to use a global variable with a timeout.

You specify a certain amount of time on the timeout, for this example 250ms. Every time the scroll event fires, this timeout is saved to a variable. Before that variable is set though, we clear it. That way, if there are consecutive calls within that 250ms, the timeout is cleared and overridden, instead of executing.

Whatever the time you put on the timeout is the amount of time that must elapse before it will be executed. It’s a simple trick that works wonders.

In The Viewport

Now that we are listening to the scroll, we need to check to see if what’s in the viewport. The way we do that is with the following function.

var IsInViewport = function(el) {
    var rect = el.getBoundingClientRect();
    return (
        rect.top <= (window.innerHeight || document.documentElement.clientHeight)
    );
};

This function takes an element, which would be our image, or whatever element we want to check for, perhaps a container, and sees if it is visible a the current time.

So every time we have a scroll timeout execute, we can loop through all the images we want to lazy load, and check which ones are in the viewport. If they are in the viewport, then we can move on to loading the image.

Loading Image

Loading the actually image is the easy part. It’s fairly straightforward.

var LazyLoadImageCreate = function(el) {
    var src = el.querySelector("noscript").innerHTML;
        src = src.substring(src.indexOf("src") + 5);
        src = src.substring(0, src.indexOf("\""));
    var alt = el.querySelector("noscript").innerHTML;
        alt = alt.substring(alt.indexOf("alt") + 5);
        alt = alt.substring(0, alt.indexOf("\""));
    var img = new Image();
    img.onload = function() {
        // Add image to the page
    }
    img.src = src;
    img.alt = alt;
};

We create a new image element and save it to a variable. Once we attach a function for the onload event, we give it a src and an alt. This tells the browser to go ahead and find this image. Once it loads, the onload event fires and we can move on to the next step: adding it to the page.

Add To The Page

Once we have our image, all we need to do is take the image element and add it to the the container.

var LazyLoadAddImage = function(el, img) {
    var div = document.createElement('div');
    div.appendChild(img.cloneNode());
    el.innerHTML = div.innerHTML;
}

Putting It All Together

See the Pen Basic Lazy Loader by Kyle Hawk (@Krazyhawk) on CodePen.

There you have it. Easy right?

There are a number of things that could be done to improve it, but this is a good starting point. One thing I, personally, have adjusted has been the ability to load in a webp alternate version if it is available, since I love webp. Something else would be a loading animation of some sort. Another simple addition could be a handle for if the loading image fails.

Thanks for reading, hopefully it helps!

Some more articles for you

Coding

May 29, 2019

Intersection Observer: A Great New API

Finding out if an element is within the viewport has always been a pain in the butt. Luckily, there's a handy new API called Intersection Observer.

View post

Coding

March 21, 2019

Weird Google Chrome CSS Transition On Load Bug

I was working on a project when I noticed the CSS transitions were firing on page load...which was not supposed to happen. So...what the heck?

View post