What is lazy loading
Lazy loading (lazy loading) is a strategy of loading resources only when they are needed.
Principle: When the user scrolls the page, determine whether the element is in the visible area, and the element resources outside the visible area (such as pictures) will not load;
Why: This is a way to shorten the length of critical render paths, which can shorten page load times. Lazy loading is commonly used to implement functions such as:
- Lazy loading of images
- Infinite scrolling of lists
- Calculate exposure of AD elements
- Clickable link preloading
Lazy loading scheme
Knowing that the core principle of lazy loading is to determine whether the element is in the visible area, there are two ways to achieve:
The first is to determine the position of the current relative view of an element when listening for the Scroll event
The other is through IntersectionObserver (overlapping observer, which is used to judge whether two elements overlap without monitoring events and compatibility). Existing schemes:
-
lazysizes
:Github.com/aFarkas/laz… -
vue-lazyload
:Github.com/hilongjw/vu… Lozad.js
:Github.com/ApoorvSaxen…
All three schemes are compatible and IntersectionObserver API is used. In other words, if the browser supports IntersectionObserver, the browser API is directly used. If not, the scrolllistener listens for elements appearing or approaching the viewport area. Here’s a quick look at each implementation:
lazysizesimplementation
Lazysizes core source is lazysizes-core.js and lazysizes-intersection.js, where the implementation of lazysizes-core scroll monitor, Lazyzes-intersection is an implementation of IntersectionObserver. First we define the self-executing function (code), define the default parameter variable, and merge it if the user has a custom parameter lazySizesConfig.
(function(){
var prop;
var lazySizesDefaults = {
lazyClass: 'lazyload'.loadedClass: 'lazyloaded'.loadingClass: 'lazyloading'./ /... omit
};
lazySizesCfg = window.lazySizesConfig || window.lazysizesConfig || {};
// Merge the default configuration
for(prop in lazySizesDefaults){
if(! (propinlazySizesCfg)){ lazySizesCfg[prop] = lazySizesDefaults[prop]; }}}) ();Copy the code
Loader._ () ¶ Loader._ () ¶ Loader._ () ¶ loader._() ¶
_ :function(){
// Get the DOM element that needs lazyLoad
// The lazySizescfg. lazyClass configuration item can be customized
lazysizes.elements = document.getElementsByClassName(lazySizesCfg.lazyClass);
// Listen for scroll
addEventListener('scroll', throttledCheckElements, true);
}
Copy the code
The main implementation of the listener is throttledCheckElements, but it is actually called checkElements. Throttling is performed for performance reasons. It is interesting to see its throttling implementation (code), as well as the implementation of requestIdleCallback. Take a look at how checkElements checks if an element is in the viewable area and how it replaces the real resource in the attribute.
// Trigger distance
if(! defaultExpand) { defaultExpand = (! lazySizesCfg.expand || lazySizesCfg.expand <1)? docElem.clientHeight >500 && docElem.clientWidth > 500 ? 500 : 370 :
lazySizesCfg.expand;
}
/ /... omit
// If the trigger condition is met when traversing the current element
rect = lazyloadElems[i].getBoundingClientRect();
if ((eLbottom = rect.bottom) >= elemNegativeExpand &&
(eLtop = rect.top) <= elvH &&
(eLright = rect.right) >= elemNegativeExpand * hFac &&
(eLleft = rect.left) <= eLvW &&
(eLbottom || eLright || eLleft || eLtop) &&
(lazySizesCfg.loadHidden || isVisible(lazyloadElems[i])) &&
((isCompleted && isLoading < 3 && !elemExpandVal && (loadMode < 3 || lowRuns < 4)) || isNestedVisible(lazyloadElems[i], elemExpand))){
unveilElement(lazyloadElems[i]);
loadedSomething = true;
if(isLoading > 9) {break;}
}
Copy the code
When iterating through the current element, judge that the trigger condition is met and lazyUnveil (code) in unveilElement is called to complete the attribute replacement. Moreover, IntersectionObserver can be implemented in the same way, but IntersectionObserver and Scroll can judge in a different way.
Loza.jsimplementation
The implementation uses the Intersection Observer API and MutationObserver directly for better performance. The code is only ~ 1K, which is very lightweight. Add a comment to the code:
export default function (selector = '.lozad', options = {}) {
// Merge custom configuration with default configuration
const {root, rootMargin, threshold, enableAutoReload, load, loaded} = Object.assign({}, defaultConfig, options)
// Initializes the observer, mutationObserver does the element listener and passes the listener callback
let observer
let mutationObserver
// Determine whether IntersectionObserver is supported
if (support('IntersectionObserver')) {
observer = new IntersectionObserver(onIntersection(load, loaded), {
root,
rootMargin,
threshold
})
}
// Check whether MutationObserver is supported, enableAutoReload is automatically reloaded when the property changes
if (support('MutationObserver') && enableAutoReload) {
mutationObserver = new MutationObserver(onMutation(load, loaded))
}
// According to the selector selector, get all the DOM nodes and iterate over them
const elements = getElements(selector, root)
for (let i = 0; i < elements.length; i++) {
// Large image optimization, preLoad method implemented, when the large image is not loaded, can be customized placeholder background
preLoad(elements[i])
}
return {
// External call: Register the observed, that is, register the DOM node that needs to be observed
observe() {
const elements = getElements(selector, root)
/ / traverse
for (let i = 0; i < elements.length; i++) {
if (isLoaded(elements[i])) {
continue
}
if (observer) {
if (mutationObserver && enableAutoReload) {
mutationObserver.observe(elements[i], {subtree: true.attributes: true.attributeFilter: validAttribute})
}
Observe.observe (target) is a simple way to register the observed
observer.observe(elements[i])
continue
}
load(elements[i])
markAsLoaded(elements[i])
loaded(elements[i])
}
},
triggerLoad(element) {
if (isLoaded(element)) {
return
}
load(element)
markAsLoaded(element)
loaded(element)
},
observer,
mutationObserver
}
}
Copy the code
Lazy loading principle: the realization of visual area judgment
Look at the implementation of their respective schemes above, in fact, are around the principle of lazy loading, and then expand their functions. So the main thing to understand is the core implementation, so let’s learn how to determine if an element is in the viewable area.
Method 1: getBoundingClientRect
GetBoundingClientRect is a free method for the element. The return value is a DOMRect object with attributes left, top, right, bottom, x, y, width, and height to indicate the relative position of the element in the view. Unfamiliar people are strongly advised to read the documentation
The Element.getBoundingClientRect() method returns a DOMRect object providing information about the size of an element and its position relative to the viewport.
Based on the above figure, we can conclude that the conditions for determining whether an element is in a view are:top >= 0 && bottom <= viewHeight
(Window height) andLeft >= 0 && right <= viewWidth
Hence the following code:
function checkElement(element) :boolean {
const viewWidth = window.innerWidth || document.documentElement.clientWidth
const viewHeight = window.innerHeight || document.documentElement.clientHeight
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect() || {}
return (
top >= 0 &&
bottom <= viewHeight &&
left >= 0 &&
right <= viewWidth
);
}
Copy the code
Mode 2: IntersectionObserver
The nice performance!!!!! Usage is also strongly recommended
The IntersectionObserver interface (which is part of the IntersectionObserver API) provides a way to asynchronously observe the intersecting status of a target element with its ancestor element or top-level document viewport. Ancestor elements and viewports are called roots. When a IntersectionObserver object is created, it is configured to listen on a given proportion of visible areas in the root. Once IntersectionObserver is created, its configuration cannot be changed, so a given observer object can only be used to monitor specific changes in the visible area. However, you can configure to listen on multiple target elements in the same observer object.
Combined with the implementation of the existing library scheme above, it can be obtained:
// IntersectionObserver attributes
const options = {}
const intersectionObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(entry= > {
// The trigger time
entry.time
// The position of the root element is a rectangle, in this case the window position
entry.rootBounds
// The position of the observed rectangle
entry.boundingClientRect
// The position rectangle of the overlapping area
entry.intersectionRect
// The proportion of the overlapped area to the observed area (even if the observed is not a rectangle)
entry.intersectionRatio
// Target element
entry.target
});
}, options);
// start observing
const observer = intersectionObserver.observe(document.querySelector('.scrollerFooter'));
Copy the code
Handwriting pictures lazy loading
getBoundingClientRect
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Lazyload-getBoundingClientRect</title>
<style>
img {
display: block;
margin-bottom: 50px;
height: 400px;
}
</style>
</head>
<body>
<img src="" lazyload="true" data-src="assets/images/iu.jpeg" />
<img src="" lazyload="true" data-src="assets/images/iu1.jpeg" />
<img src="" lazyload="true" data-src="assets/images/iu2.jpeg" />
<img src="" lazyload="true" data-src="assets/images/iu3.jpeg" />
<img src="" lazyload="true" data-src="assets/images/iu.jpeg" />
<img src="" lazyload="true" data-src="assets/images/iu1.jpeg" />
<img src="" lazyload="true" data-src="assets/images/iu2.jpeg" />
<img src="" lazyload="true" data-src="assets/images/iu3.jpeg" />
<script>
function checkElement(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth
const viewHeight = window.innerHeight || document.documentElement.clientHeight
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect() || {}
return (
top >= 0 &&
bottom <= viewHeight &&
left >= 0 &&
right <= viewWidth
)
}
function lazyload() {
var eles = document.querySelectorAll('img[data-src][lazyload]')
Array.prototype.forEach.call(eles, function (el, index) {
var rect
if (el.dataset.src === "")
return
if (checkElement(el)) {
!function () {
var img = new Image()
img.src = el.dataset.src
img.onload = function () {
el.src = img.src
}
el.removeAttribute("data-src")// Remove the attribute and do not iterate over it again
el.removeAttribute("lazyload")
}()
}
})
}
lazyload()// Trigger a function to initialize the first page image before scrolling
document.addEventListener("scroll", lazyload)
</script>
</body>
</html>
Copy the code
IntersectionObserver
<script>
function lazyload() {
// IntersectionObserver attributes
const options = { threshold: 1.0 }
const intersectionObserver = new IntersectionObserver(function (entries, observer) {
entries.forEach(entry= > {
console.log('entry', entry);
const el = entry.target
if (entry.isIntersecting) {
!function () {
var img = new Image()
img.src = el.dataset.src
img.onload = function () {
el.src = img.src
}
}()
}
});
}, options);
// start observing
var eles = document.querySelectorAll('img[data-src][lazyload]')
Array.prototype.forEach.call(eles, function (el, index) {
intersectionObserver.observe(el);
})
}
lazyload()
</script>
Copy the code
Attach a code
Github.com/AutumnWhj/l…
reference
GetBoundingClientRect IntersectionObserver lazy loading and preloading