Design Goal 

I want a simple solution to lazyloading data, based on the visible area of the website without using any external libraries. The most obvious use case for such a system would be the loading of images on a webpage as you scroll down. So this will also be the use case implemented in the code examples. As usual, if you only want the code you can find it on my GitHub.

HTML 

We need an attribute or something similar which identifies the relevant elements. In this example, I will use the attribute realsrc to save the URL of a picture which shall be lazyloaded and displayed as a background image for the container. This means HTML should look something like this:

<img src="" realsrc="url("test.jpg")"></img>

Event Listener 

We need event listeners that fire upon the relevant events. If you do not add an listener for load, lazyloaded elements will only be loaded once the first scroll event fires, which is generally not what you want. Likewise, if you are using a responsive design, you need to listen for resizeevents, because users might expose more visible area by resizing the windows.

window.addEventListener('scroll', refresh_handler);
window.addEventListener('load', refresh_handler);
window.addEventListener('resize', refresh_handler);

Callback 

We then need the callback function for the event listeners, which we will call refresh_handler in our example.

Getting the relevant elements 

We have given all elements which have to change in some way, an attribute called realsrc. This means we now simply select all elements which have this attribute. This isn’t very efficient, however we only have to query those elements once and then cache them in a variable. We should also remember which elements were already changed. Since the DOM is parsed top down, we can increment a counter, representing the position within the array of elements until which we have already modified the respective element, so that we do not attempt to modify them again

# outside of callback function
var elements = null
var counter  = 0

# inside of callback    
elements = document.querySelectorAll("*[realsrc]");

Getting the current view box 

The most reliable, cross-browser way of calculating the current viewbox, is by using an element or pseudo element on top of the page. On my web page I use my navigation bar for this, but you might as well just insert an empty container at the start of your HTML code and give it any unique ID.

var div_at_top  = document.getElementById("navbar")
var cur_viewbox = -div_at_top.getBoundingClientRect()

Selecting and modifying elements 

We then use a loop to iterate through the elements and modifying all that are within the viewbox. We should define an offset, so that pictures are already loaded if they are almost within the visible area.

var offset = 200
for (var i = counter; i < elements.length; i++) {

        # get position of element
        var boundingClientRect = elements[i].getBoundingClientRect();

        # modify element
        if (boundingClientRect.top < window.innerHeight + offset) {
            elements[i].style.backgroundImage = newSrc;
            elements[i].removeAttribute("realsrc");
        }else{
            /* DOM is parsed top down and images are inserted in that order too             */
            /* meaing that once we reach pic that isn't in viewbox none following will be   */
            /* the counter variable was defined in the previous section                     */
            counter = i; 
            return;
        }
}

Optional: Optimizing the execution 

Define minimum viewbox change 

We have already cached elements and guaranteed, that we do neither modify an element twice nor even check twice if it is within the current viewbox. Scroll events, unfortunately, are triggered very rapidly. Rate limiting those is difficult, so an easier, but similarly efficient solution is to have as little code as possible executed per handler. We can achieve this by checking how much the viewbox changed, at the very beginning of the handler, before doing anything else.

# outside of callback
var min_viewbox_change = 100
/* guarantee initial call evaluates to true */
var viewbox_y = -Infinity

# inside handler/callback
if(cur_viewbox - viewbox_y < min_viewbox_change){
    return;
}

Unregister Event Listeners 

Since we return from the handler at the first element that’s not visible within the loop, reaching the end of the loop and leaving it without a return-statement, means that there are no more elements to modify or load. This in return means that we can simply unregister the event-listeners, so they won’t be executed anymore anyway.

window.removeEventListener('scroll', refresh_handler);

Links/Attribution 

  • Full code example in my GitHub
  • Cover is called the “Unofficial JavaScript Logo” by Chris Williams licensed WTFPL

Feel free to send me a mail to share your thoughts!