图片懒加载实现原理
预先将图片真实的src放在自定义的属性中,data-src 当图片出现在视口范围内,将data-src赋值给src,完成图片加载
这里有个点就是初始化时可以不给img标签加上src属性,因为只要存在src属性,浏览器就会去执行一次请求将其指向的资源下载并应用到文档内,这里不加上可以提升一些性能
判断图片所在位置是否在可视区内,图片移到可视区内进行加载,
提供三种判断方法
- offsetTop < clientHeight + scrollTop
- element.getBoundingClientRect().top < clientHeight
- IntersectionObserver
引用
作者:程洋cYang
链接:juejin.cn/post/689681…
来源:稀土掘金
js实现部分 /第二种
-
获取浏览器视口的高度
获取可视区域的高度我们通常使用
window.innerHeight就可以拿到了,当然如果需要兼容低版本IE浏览器的话则可以使用document.documentElement.clientHeight来获取,这里我们做一个兼容处理const viewPortHeight = window.innerHeight || document.documentElement.clientHeight
-
获取图片离顶部的距离
直接使用
getBoundingClientRect()这个方法来获取 -
**Element.getBoundingClientRect()**方法返回元素的大小及其相对于视口的位置。
一个小疑问?
人家的库用的offsetTop还是getBoundingClientRect呢 ?
哈哈 是intersectionObserver不
x : 146
y : 50
width : 440
height : 240
top : 50
right : 586
bottom : 290
left : 146
// 获取所有图片
const imgList = document.querySelectorAll('img')
// 用于记录当前显示到了哪一张图片
let index = 0;
function lazyload() {
// 获取浏览器视口高度,这里写在函数内部是考虑浏览器窗口大小改变的情况
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight
for (let i = index; i < imgList.length; i++) {
// 这里用可视区域高度减去图片顶部距离可视区域顶部的高度
const distance = viewPortHeight - imgList[i].getBoundingClientRect().top;
// 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明图片已经出现在了视口范围内
if (distance >= 0) {
// 给图片赋值真实的src,展示图片
imgList[i].src = imgList[i].getAttribute('data-src');
// 前i张图片已经加载完毕,下次从第i+1张开始检查是否需要显示
index = i + 1;
}
}
}
// 定义一个防抖函数
function debounce(fn, delay = 500) {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 页面加载完成执行一次lazyload,渲染第一次打开的网页视口内的图片
window.onload = lazyload;
// 监听Scroll事件,为了防止频繁调用,使用防抖函数进行优化
window.addEventListener("scroll", debounce(lazyload, 600));
// 浏览器窗口大小改变时重新计算
window.addEventListener("resize", debounce(lazyload, 600));
vue中的图片懒加载
vue3,我们可以使用 @vueuse/core 中的 useIntersectionObserver 来实现监听组件进入可视区域行为,需要配合vue3.0的组合API的方式才能实现
vue2
IntersectionObserver() 构造器
使用
var observer = new IntersectionObserver(callback[, options]);
callback
-
entries一个
IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。 -
observer被调用的
IntersectionObserver实例。
非传统实现方式,性能最优
/**
* 懒加载
* @description 可加载`<img>`、`<video>`、`<audio>`等一些引用资源路径的标签
* @param {object} params 传参对象
* @param {string?} params.lazyAttr 自定义加载的属性(可选)
* @param {"src"|"background"} params.loadType 加载的类型(默认为`src`)
* @param {string?} params.errorPath 加载失败时显示的资源路径,仅在`loadType`设置为`src`中可用(可选)
*/
function lazyLoad(params) {
const attr = params.lazyAttr || "lazy";
const type = params.loadType || "src";
/** 更新整个文档的懒加载节点 */
function update() {
const els = document.querySelectorAll(`[${attr}]`);
for (let i = 0; i < els.length; i++) {
const el = els[i];
observer.observe(el);
}
}
/**
* 加载图片
* @param {HTMLImageElement} el 图片节点
*/
function loadImage(el) {
const cache = el.src; // 缓存当前`src`加载失败时候用
el.src = el.getAttribute(attr);
el.onerror = function () {
el.src = params.errorPath || cache;
}
}
/**
* 加载单个节点
* @param {HTMLElement} el
*/
function loadElement(el) {
switch (type) {
case "src":
loadImage(el);
break;
case "background":
el.style.backgroundImage = `url(${el.getAttribute(attr)})`;
break;
}
el.removeAttribute(attr);
observer.unobserve(el);
}
/**
* 监听器
* [MDN说明](https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver)
*/
const observer = new IntersectionObserver(function(entries) {
for (let i = 0; i < entries.length; i++) {
const item = entries[i];
if (item.isIntersecting) {
loadElement(item.target);
}
}
})
update();
return {
observer,
update
}
}
复制代码
在vue中使用指令去使用
import Vue from "vue";
/** 添加一个加载`src`的指令 */
const lazySrc = lazyLoad({
lazyAttr: "vlazy",
errorPath: "./img/error.jpg"
})
Vue.directive("v-lazy", {
inserted(el, binding) {
el.setAttribute("vlazy", binding.value); // 跟上面的对应
lazySrc.observer.observe(el);
}
})
/** 添加一个加载`background`的指令 */
const lazyBg = lazyLoad({
lazyAttr: "vlazybg",
loadType: "background"
})
Vue.directive("v-lazybg", {
inserted(el, binding) {
el.setAttribute("vlazybg", binding.value); // 跟上面的对应
lazyBg.observer.observe(el);
}
})