Defer scripts until they are needed
Learn how to fix the Core Web Vitals by deferring scripts until they are needed
Defer scripts until they are needed
In this article I will show and explain a cool pattern to load scripts that are not needed during the start of the pageload at a later time, just before they are needed.
The absolute most effective thing you can do with JavaScript when it comes to the Core Web Vitals is to delay loading of a resource until it is needed. This will remove unused and unneeded JavaScript from the page and only load it when it is needed. This will fix the lighthouse warning 'reduce unused JavaScript' and also improve responsivity metrics like the interaction to Next Paint (INP).
We have been doing this with images for a long time. It is called lazy loading. With lazy loading a below-the-fold image is loaded right before it scrolls into view. This way we do not need to load the image immediately during page load and the browser can spend its precious resources on downloading, parsing and painting things that are actually needed.
Now imagine we could do the same thing with Scripts instead of images. Well it turns out that we can! Unfortunately it is not as simple as adding loading="lazy"
to an image but with a bit of effort we can make it work
Step 1: Load scripts on demand
To add scripts to the page after page load we will need a small script that does this for us.
function injectScript(scriptUrl, callback) {
var script = document.createElement("script");
script.src = scriptUrl;
if (typeof callback === "function") {
script.onload = function () {
callback();
};
}
document.head.appendChild(script);
}
This function injects a script into the current web page by creating a new script element and appending it to the head of the document. The scriptUrl parameter specifies the URL of the script to be injected. The callback parameter is an optional function that will be called when the script has finished loading. When the script has finished loading, the onload event of the script element is triggered. If a callback function was provided, it will be called at this point.
Step 2: Load scripts on demand
The next step is to load scripts on demand. There are 2 common methods of doing this. The first is the more reliable 'when a part of the page is visible' and the second is the faster 'on interaction'.
2a: Intersection observer
The first method to load a script just before it is needed makes use of the intersection observer. The intersection observer is a reliable method that 'fires' when an element is intersecting with the visible part of the screen. We can use this behaviour to trigger a script download only when an element is visible. The downside of this method is that even though an element is 'on screen', it still might not be used.
function injectScriptOnIntersection(scriptUrl, elementSelector) {
var observer = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
injectScript(scriptUrl);
observer.unobserve(entry.target);
}
});
});
var element = document.querySelector(elementSelector);
observer.observe(element);
}
This function takes two parameters: scriptUrl is the URL of the script to be injected, and elementSelector is a CSS selector for the element that should trigger the injection.
The function creates a new IntersectionObserver object and passes it a callback function that will be called whenever an observed element intersects with the viewport. The callback function checks if the element is intersecting and, if so, injects the script and stops observing the element.
Note that the Intersection Observer API is not supported in all browsers, so you may need to use a polyfill if you need to support older browsers.
injectScriptOnIntersection('script.js', '#my-element');
This will inject the script when the element with ID my-element becomes visible in the viewport.
2b: On interaction
The most effective method to load JavaScript on demand is to load it when a visitor interacts with a certain element. For example a form. The advantage of using this method is that you will probably never load the script if it is not needed. The downside is that the download action is pretty late and we have to decide which events (mousover, hover, touchstart etc etc) we want to listen for.
function injectScriptOnInteraction(scriptUrl, elementSelector, eventTypes) {
var element = document.querySelector(elementSelector);
var eventHandler = function() {
injectScript(scriptUrl);
eventTypes.forEach(function(eventType) {
element.removeEventListener(eventType, eventHandler);
});
};
eventTypes.forEach(function(eventType) {
element.addEventListener(eventType, eventHandler);
});
}
This function takes three parameters: scriptUrl is the URL of the script to be injected, elementSelector is a CSS selector for the element that should trigger the injection, and eventTypes is an array of event types that should trigger the injection (e.g. ["click", "mouseover"]).
The function finds the element using document.querySelector and adds event listeners to it for each of the specified event types. When any of the specified events occur, the injectScript function is called with the specified URL, and the event listeners are removed using element.removeEventListener.
injectScriptOnInteraction(
'script.js',
'#my-element',
['click', 'mouseover']
);
This will inject the script when the element with ID my-element is clicked or hovered over, and then remove the event listeners.
Conclusion
When scripts are not needed right away during the start op pageload it is a great idea to load them on demand! We can do this by using the intersection observer or on interaction. This will free up valuable resources during the early stages of pageload
Need your site lightning fast?
Join 500+ sites that now load faster and excel in Core Web Vitals.
- Fast on 1 or 2 sprints.
- 17+ years experience & over 500 fast sites
- Get fast and stay fast!