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 aclass
attribute value oflazy
conflicts with your application.throttleTime
(default is200
): In cases where Intersection Observer isn't available, standard event handlers are used.throttleTime
allows you to control how often the code within these event handlers fire in milliseconds.idlyLoad
(default isfalse
): If set totrue
,requestIdleCallback
is used to optimize use of browser idle time to limit monopolization of the main thread. Notes: This setting is ignored if set totrue
in 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 thethreshold
option if you set this option totrue
!idleLoadTimeout
(default is100
): IfidlyLoad
is set totrue
, this option sets a deadline in milliseconds forrequestIdleCallback
to 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 totrue
in a browser that doesn't support Mutation Observer!observeRootSelector
(default is"body"
): IfobserveChanges
is set totrue
, the value of this string is fed intodocument.querySelector
to limit the scope in which the Mutation Observer looks for DOM changes.document.body
is 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 theMutationObserver
instance. 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!