Design Goal
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 which fires upon the relevant events. If you do not add a 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 resize
events, because users might expose more visible area by resizing the window.
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 any contained 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!