yall.js is a featured-packed lazy loading script for <img>, <picture>, <video> and <iframe> elements. It works in all modern browsers including IE11. It uses Intersection Observer where available, but falls back to scroll, touchmove, resize, and orientationchange events where necessary. It can also monitor the DOM for changes using Mutation Observer to lazy load image elements that have been appended to the DOM after initial page render, which may be desirable for single page applications. It can also (optionally) optimize use of browser idle time using requestIdleCallback. To optimize decoding of <img> lazy loading for simple src and srcset use cases, yall.js uses Image.decode where available to decode images asynchronously before adding them to the DOM.
This is version 2 of yall.js, and introduces breaking changes over version 1. While version 1 only required you to include the script and tag elements with a class of lazy, this script must be explicitly initialized like so:
<script src="yall.min.js"></script>
<script>document.addEventListener("DOMContentLoaded", yall);</script>The above syntax is sufficient if you don't want to pass in any options. If you want to specify options, however, you'll need to use a slightly more verbose syntax:
<script src="yall.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
yall({
observeChanges: true
});
});
</script>From there, lazy loading elements with yall.js is simple!
Let's look at the simplest <img> element use case:
<!-- A simple src-only <img> element example -->
<img class="lazy" src="placeholder.jpg" data-src="image-to-lazy-load.jpg" alt="Alternative text to describe image.">In this case, we specify an optional placeholder image in the src attribute, and point to the image we want to lazy load in the data-src attribute. Attaching a class of lazy exposes elements to yall.js, and is necessary for the lazy loader to work (although this class value can be overridden via the API options). Let's look at an example using both src and srcset:
<!-- A somewhat more complex src + srcset example -->
<img class="lazy" src="placeholder.jpg" data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x" data-src="image-to-lazy-load-1x.jpg" alt="Alternative text to describe image.">Since <picture> is a thing now, yall.js supports that, too:
<!-- A more complex <picture> + <img> + src/srcset example -->
<picture>
<source data-srcset="image-to-lazy-load-2x.webp 2x, image-to-lazy-load-1x.webp 1x" type="image/webp">
<img class="lazy" src="placeholder.jpg" data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x" data-src="image-to-lazy-load-1x.jpg" alt="Alternative text to describe image.">
</picture>You can also use yall.js to lazy load <video> elements! This could be useful if you are replacing animated GIF with autoplaying video:
<video class="lazy" autoplay loop muted playsinline>
<source data-src="video.webm" type="video/webm">
<source data-src="video.mp4" type="video/mp4">
</video>The pattern is largely the same as it is with the <picture> use case, only the lazy class is applied to the <video> element. Tip: If you're embedding videos that don't emulate animated GIF behavior (i.e., non autoplaying video), it's better to not lazy load them. Instead, lean on the preload attribute to defer loading of video content. Please note that video autoplay policies may change at any time, meaning your video may not autoplay on some platforms!
As of version 2, you can also lazy load <iframe>s! This looks pretty much just like a simple <img> example:
<iframe class="lazy" data-src="some-other-document.html"></iframe>Easy! Slap on some <noscript> goodness:
<!-- A <noscript> example using <img> with src and srcset. -->
<img class="lazy" data-srcset="/img/image-to-lazy-load-2x.jpg 2x, /img/image-to-lazy-load-1x.jpg 1x" data-src="/img/image-to-lazy-load-1x.jpg" src="/img/placeholder.jpg" alt="Alternative text to describe image.">
<noscript>
<img srcset="/img/image-to-lazy-load-2x.jpg 2x, /img/image-to-lazy-load-1x.jpg 1x" src="/img/image-to-lazy-load-1x.jpg" alt="Alternative text to describe image.">
</noscript>
<!-- And a <picture> example. -->
<picture>
<source data-srcset="/img/image-to-lazy-load-2x.webp 2x, /img/image-to-lazy-load-1x.webp 1x" type="image/webp">
<img class="lazy" data-srcset="/img/image-to-lazy-load-2x.jpg 2x, /img/image-to-lazy-load-1x.jpg 1x" data-src="/img/image-to-lazy-load-1x.jpg" src="/img/placeholder.jpg" alt="Alternative text to describe image.">
</picture>
<noscript>
<picture>
<source srcset="/img/image-to-lazy-load-2x.webp 2x, /img/image-to-lazy-load-1x.webp 1x" type="image/webp">
<img srcset="/img/image-to-lazy-load-2x.jpg 2x, /img/image-to-lazy-load-1x.jpg 1x" src="/img/image-to-lazy-load-1x.jpg" alt="Alternative text to describe image.">
</picture>
</noscript>
<!-- Here's a <video> example, too. -->
<video class="lazy" autoplay loop muted playsinline>
<source data-src="video.webm" type="video/webm">
<source data-src="video.mp4" type="video/mp4">
</video>
<noscript>
<video autoplay loop muted playsinline>
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
</video>
</noscript>
<!-- Here's an <iframe> example for good measure. -->
<iframe class="lazy" data-src="lazy.html"></iframe>
<noscript>
<iframe src="lazy.html"></iframe>
</noscript>Then place a no-js class on the <html> element like so:
<html class="no-js">Finally, add this one line <script> before any <link> or <style> elements in the document <head>:
<!-- Remove the no-js class on the <html> element if JavaScript is on -->
<script>document.documentElement.classList.remove("no-js")</script>Normally, this script will remove the no-js class from the <html> element as the page loads, but if JavaScript is turned off, this will never happen. From there, you can add some CSS that hides elements with a class of lazy when the no-js class is present on the <html> element:
/* Hide .lazy elements if JavaScript is off */
.no-js .lazy {
display: none;
}To see all use cases in action, check out the demos in the test folder.
When you call the main yall initializing function, you can pass an in an options object. Here are the current options available:
lazyClass(default is"lazy"): The element class used by yall.js to find elements to lazy load. Change this is if aclassattribute value oflazyconflicts with your application.throttleTime(default is200): In cases where Intersection Observer isn't available, standard event handlers are used.throttleTimeallows you to control how often the code within these event handlers fire in milliseconds.idlyLoad(default isfalse): If set totrue,requestIdleCallbackis used to optimize use of browser idle time to limit monopolization of the main thread. Notes: This setting is ignored if set totruein a browser that doesn't supportrequestIdleCallback! Additionally, enabling this could cause lazy loading to be delayed significantly more than you might be okay with! This option trades off some degree of seamless lazy loading in favor of optimized use of browser idle time. Test extensively, and consider increasing thethresholdoption if you set this option totrue!idleLoadTimeout(default is100): IfidlyLoadis set totrue, this option sets a deadline in milliseconds forrequestIdleCallbackto kick off lazy loading for an element.threshold(default is200): The threshold (in pixels) for how far elements need to be within the viewport to begin lazy loading. This value affects lazy loading initiated by both Intersection Observer and legacy event handlers.observeChanges(default isfalse): Use a Mutation Observer to examine the DOM for changes. This is useful if you're using yall.js in a single page application and want to lazy load resources for markup injected into the page after initial page render. Note: This option is ignored if set totruein a browser that doesn't support Mutation Observer!observeRootSelector(default is"body"): IfobserveChangesis set totrue, the value of this string is fed intodocument.querySelectorto limit the scope in which the Mutation Observer looks for DOM changes.document.bodyis inferred by default, but you can confine it to any valid CSS selector (e.g.,div#main-wrapper).mutationObserverOptions(default is{childList: true}): Options to pass to theMutationObserverinstance. Read this MDN guide for a list of options.
yall.js doesn't care about placeholders. It won't try to minimize layout shifting or perform layout calculations for you. Use appropriate width and height attributes on elements, as well as a placeholder (preferably something as lightweight as possible). For <video> elements, use the poster attribute to set a placeholder image. Please check out the test folder to see how you might use placeholders in conjunction with yall.js. If you don't want to bother with placeholders, you can omit the src attribute entirely in your lazy loading markup, and yall.js will still work.
Also, do not lazy load critical resources that are above the fold. Doing so is a performance anti-pattern, because those resources will not begin loading until yall.js has been loaded, which may take much longer than if the resources was loaded normally. Be smart. If the resource is critical, load it normally! If it's below the fold, then lazy load it!
If you have an idea, file an issue and let's talk about it. Unsolicited pull requests for new features will generally be rejected unless those requests contain bug fixes.
Thank you to BrowserStack for graciously providing free cross-platform browser testing services!
