This article is featured on Github github.com/Michael-lzg…
Demo source address github.com/Michael-lzg…
In e-commerce projects, there are often a large number of pictures, such as banner advertising picture, menu navigation picture, meituan and other business list head picture. The large number of pictures and the large size of pictures often affect the page loading speed, resulting in a bad user experience, so it is imperative to optimize the lazy loading of pictures.
Why lazy image loading
Let’s take a look at the image information that loads when the page launches.
, as shown on this page load when starting a few images (or more), and these pictures request is almost simultaneous, in Chrome, maximum number of concurrent requests are limited and the request of the other will be pushed to the queue waiting or stagnate, until last round after the completion of the new request. Therefore, a considerable number of image resource requests require queuing time.
As can be seen from the above, some pictures reach hundreds of kB, and the setting is 2M. , directly resulting in a long loading time.
In view of the above situation, image lazy loading has the following advantages:
- Reduce the load of resources, the page only load the first screen image, this can significantly reduce the server pressure and traffic, also can reduce the burden of the browser.
- Prevent the concurrent loading of resources too much and block JS loading, affecting the start of the entire site.
- It can improve user experience. Imagine that when users open a page, if all the pictures on the page need to be loaded, due to the large number of pictures, the waiting time will be long, which will seriously affect user experience.
Image lazy loading principle
The principle of picture lazy loading is mainly to judge whether the current picture has reached the visual area of the core logic
- Get all the pictures dome.
- Walk through each image to determine if the current image is in the viewable area.
- If it does, set the image’s SRC property.
- The binding of window
scroll
Event to listen for events.
Let’s take a look at the page structure first
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Lazyload</title>
<style>
img {
display: block;
margin-bottom: 50px;
height: 200px;
width: 400px;
}
</style>
</head>
<body>
<img src="./img/default.png" data-src="./img/1.jpg" />
<img src="./img/default.png" data-src="./img/2.jpg" />
<img src="./img/default.png" data-src="./img/3.jpg" />
<img src="./img/default.png" data-src="./img/4.jpg" />
<img src="./img/default.png" data-src="./img/5.jpg" />
<img src="./img/default.png" data-src="./img/6.jpg" />
<img src="./img/default.png" data-src="./img/7.jpg" />
<img src="./img/default.png" data-src="./img/8.jpg" />
<img src="./img/default.png" data-src="./img/9.jpg" />
<img src="./img/default.png" data-src="./img/10.jpg" />
</body>
</html>
Copy the code
First to get all the pictures of the dom, through the document. The body. The clientHeight get highly visual area, then use element. GetBoundingClientRect () API directly to get the top value of the element relative to browse, Walk through each image to determine if the current image is in the viewable area. The code is as follows:
function lazyload() {
let viewHeight = document.body.clientHeight // Get the viewable height
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item, index) = > {
if (item.dataset.src === ' ') return
// Get the left, up, right, and down positions of an element on the page relative to the browser window
let rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')}}}Copy the code
Finally, bind the onScroll event to the window
window.addEventListener('scroll', lazyload)
Copy the code
The main completion of an image lazy loading operation. However, there is a big performance problem, because the Scroll event will trigger many times in a very short time, which seriously affects the page performance. In order to improve the page performance, we need a throttling function to control the function trigger many times, and only perform one callback in a period of time (such as 200ms).
Let’s implement a throttling function
function throttle(fn, delay) {
let timer
let prevTime
return function (. args) {
const currTime = Date.now()
const context = this
if(! prevTime) prevTime = currTimeclearTimeout(timer)
if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
clearTimeout(timer)
return
}
timer = setTimeout(function () {
prevTime = Date.now()
timer = null
fn.apply(context, args)
}, delay)
}
}
Copy the code
Then modify the SRCOLl event
window.addEventListener('scroll', throttle(lazyload, 200))
Copy the code
IntersectionObserver
Through the implementation of the above example, if we want to realize lazy loading, we need to listen for scroll events. Although we can prevent high frequency function execution by function throttling, we still need to calculate the properties of scrollTop, offsetHeight and so on. Is there a simple way not to calculate these attributes? The answer is IntersectionObserver.
IntersectionObserver is a new API that automatically “observes” whether elements are visible and is already supported by Chrome 51+. Because the nature of visible is that a target element intersects with the viewport, the API is called a “intersecting viewer.” Let’s see how it’s used:
var io = new IntersectionObserver(callback, option)
// Start observing
io.observe(document.getElementById('example'))
// Stop observing
io.unobserve(element)
// Close the viewer
io.disconnect()
Copy the code
IntersectionObserver is a constructor provided natively by the browser and accepts two parameters: callback is the callback function when visibility changes and option is the configuration object (this parameter is optional).
When the visibility of the target element changes, the observer’s callback function is called. Callback typically fires twice. Once the target element has just entered the viewport (becoming visible), and once it has completely left the viewport (becoming invisible).
var io = new IntersectionObserver((entries) = > {
console.log(entries)
})
Copy the code
The parameters of the callback function (entries) is an array, each member is a IntersectionObserverEntry object. For example, if the visibility of two observed objects changes at the same time, the Entries array will have two members.
- Time: indicates the time when visibility changes. It is a high-precision timestamp in milliseconds
- Target: The observed target element, which is a DOM node object
- IsIntersecting: Whether the target is visible
- RootBounds: Information about the rectangular region of the root element,
getBoundingClientRect()
Method, or null if there is no root element (that is, scrolling directly relative to the viewport) - BoundingClientRect: Information about the rectangular region of the target element
- IntersectionRect: information on the intersection area between target element and viewport (or root element)
- IntersectionRatio: intersectionRatio of target elements, i.e
intersectionRect
占boundingClientRect
Is 1 when completely visible and less than or equal to 0 when completely invisible
Next, we use IntersectionObserver to achieve lazy image loading
const imgs = document.querySelectorAll('img[data-src]')
const config = {
rootMargin: '0px'.threshold: 0,}let observer = new IntersectionObserver((entries, self) = > {
entries.forEach((entry) = > {
if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')}// Remove the observation
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) = > {
observer.observe(image)
})
Copy the code
Lazy loading instruction
In addition to the commonly used V-show, V-bind, V-for commands, Vue can also be customized commands. The Vue directive definition function provides several hook functions (optionally) :
- Bind: called once, when the directive is first bound to an element. You can define an initialization action that is performed once at binding time.
- Inserted: Called when the bound element is inserted into its parent (the parent is called if it exists, not in the document).
- Update: Called when the template to which the element is bound is updated, regardless of whether the binding value changes. By comparing the binding values before and after the update.
- ComponentUpdated: Called when the template to which the bound element belongs completes an update cycle.
- Unbind: Called only once, when an instruction is unbound from an element.
Implement a lazy load instruction train of thought
- Determine whether the browser supports it
IntersectionObserver
API, use it if supportedIntersectionObserver
Implement lazy loading, otherwise usesrcoll
Event monitoring + throttling method implementation. - through
Vue.directive
Sign up for av-lazy
To expose oneinstall()
Function to be called by Vue. - in
main.js
Use (instruction) can be called. - In the component
<img>
Of the labelsrc
Switch tov-lazy
Can realize lazy loading pictures.
The following code
Create a new lazyload.js file
const LazyLoad = {
/ / install method
install(Vue, options) {
const defaultSrc = options.default
Vue.directive('lazy', {
bind(el, binding) {
LazyLoad.init(el, binding.value, defaultSrc)
},
inserted(el) {
if (IntersectionObserver) {
LazyLoad.observe(el)
} else {
LazyLoad.listenerScroll(el)
}
},
})
},
/ / initialization
init(el, val, def) {
el.setAttribute('data-src', val)
el.setAttribute('src', def)
},
// Use IntersectionObserver to monitor EL
observe(el) {
var io = new IntersectionObserver((entries) = > {
const realSrc = el.dataset.src
if (entries[0].isIntersecting) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
})
io.observe(el)
},
// Listen for scroll events
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll'.() = > {
handler(el)
})
},
// Load the real image
load(el) {
const windowHeight = document.documentElement.clientHeight
const elTop = el.getBoundingClientRect().top
const elBtm = el.getBoundingClientRect().bottom
const realSrc = el.dataset.src
if (elTop - windowHeight < 0 && elBtm > 0) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')}}},/ / throttling
throttle(fn, delay) {
let timer
let prevTime
return function (. args) {
const currTime = Date.now()
const context = this
if(! prevTime) prevTime = currTimeclearTimeout(timer)
if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
clearTimeout(timer)
return
}
timer = setTimeout(function () {
prevTime = Date.now()
timer = null
fn.apply(context, args)
}, delay)
}
},
}
export default LazyLoad
Copy the code
Use directive in main.js
import LazyLoad from './LazyLoad.js'
Vue.use(LazyLoad, {
default: 'xxx.png',})Copy the code
Change the SRC of the tag in the component to V-lazy
<img v-lazy="xxx.jpg" />
Copy the code
This completes a vue lazy loading command.
summary
- Lazy loading of images is necessary to improve site loading performance.
- The implementation principle of lazy image loading is to judge whether the current image is loaded in the visible area. The function can be realized by monitoring scroll events and IntersectionObserver.
- You can use vue. directive to write lazy image loading directives.