图片懒加载
图片懒加载,即当页面滚动到图片可视区或指定一段区域时再发送请求加载图片。
主要思路
根据 Element.getBoundingClientRect() 获取元素相对于视口的位置,
获取相对视口左上角计算的 top, left, right, bottom,x,y 等信息
当 top 小于视口高度或指定距离时即可进行图片加载
文档视口高度可以根据 document.documentElement.clientHeight 获取
插件实现
这里根据上述思路封装一个更为通用的 vue 指令
ts
import Vue, { DirectiveOptions } from "vue";
interface Manage {
visible: boolean; // 元素是否在可视区
every: boolean; // 是否每次从 hide -> visible 都触发
triggered: boolean; // 是否曾触发过
handler: Function;
}
const cache = { clientHeight: document.documentElement.clientHeight };
const isFn = (v: unknown): v is Function => typeof v === "function";
const store: Map<HTMLElement, Manage> = new Map();
const updateCache = () =>
(cache.clientHeight = document.documentElement.clientHeight);
const isVisible = (ele: HTMLElement) => {
const { top, bottom } = ele.getBoundingClientRect();
return top < cache.clientHeight && bottom > 0;
};
const listenScroll = () => {
for (const item of store) {
const [ele, manage] = item;
const visible = isVisible(ele);
if (manage.visible != visible) {
manage.visible = visible;
if (visible && (manage.every || !manage.triggered)) {
manage.handler(ele);
manage.triggered = true;
}
}
}
};
window.addEventListener("scroll", listenScroll);
window.addEventListener("resize", updateCache);
const visibleConfig: DirectiveOptions = {
bind(ele, { value, modifiers }) {
if (isFn(value)) {
// 确保 dom 渲染
Vue.nextTick(() => {
const visible = isVisible(ele);
const manage = {
visible,
every: modifiers.every || false,
triggered: false,
handler: value as Function,
};
if (manage.visible) {
value(ele);
manage.triggered = true;
}
store.set(ele, manage);
});
} else {
throw new TypeError("v-visible accept a function");
}
},
unbind(ele, { value, modifiers }) {
store.delete(ele);
},
};
export default visibleConfig;其他方式
针对 img 标签,高版本的浏览器提供了 loading="lazy" 属性,其在距离可视区一点距离时自动加载图片,如
html
<img src="https://some.image.com" loaing="lazy" />针对更为通用的元素,提供了 IntersectionObserver API(实验阶段),用于观察元素是否出现在可视区,如
js
const target = document.getElementById("target");
const observer = new IntersectionObserver(function (entries) {
if (entries[0].intersectionRatio <= 0) return; // intersectionRatio 为0表示目标在可视区外
console.log("进入可视区了");
});
observer.observe(target);