本文已参与「新人创作礼」活动,一起开启掘金创作之路
为什么要懒加载
图片资源体积影响加载请求,影响性能很严重
花最少的时间,最大的优化
1. 使用浏览器级别懒加载
兼容性还行,还简单
chrome 76以后出的
可以实现懒加载效果
<img src="" lazy="loading" alt="">
背景图懒加载
图片懒加载的关键在于获取元素的位置,
并判断其是否出现在视口。故有以下三种方式
滚动监听+scrollTop+offsetTop+innerHeight
滚动监听+getBoundingClientRect()
intersectionObserve()
IntersectionObserver
ntersection Observer API
可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。
意思就是浏览器可以观察元素是否在可视区内。
/*
初始化 Intersectionobserver 类
`callback`是当元素的可见性变化时候的回调函数,`options`是一些配置项
*/
const observer = new IntersectionObserver(callback, options);
/* 监听对应的 DOM 元素 */
observer.observe(DOM);
/* 取消对 DOM 元素的监听 */
observer.unobserve(DOM);
/*
root:用于观察的根元素,默认是浏览器的视口
rootMargin:用来缩放视窗的的大小,用法和 CSS 类似,
`10px 10px 30px 20px`表示top、right、bottom 和 left 扩大的对应值,
`10px 20px 5px`表示top,left&right, bottom 扩大的对应值
thresholds: 当被监听的元素与根元素的交叉比例达到该值时触发回调函数,
默认为`[0]`, `0.1`表示当交叉比例达到`0.1`时触发回调,
`[0,0.5]`表示当交叉比例达到`0`和`0.5`时都会触发回调
*/
const options = {
root: null,
rootMargin: 0,
thresholds: 1,
};
/*
回调函数在监听的 DOM 元素可见性变化时触发
entries 中有多个参数,我们使用到以下两个参数
isIntersecting 表示观察的元素是否出现在 root 元素可视区,
intersectionRatio 表示观察的元素出现在 root 元素的比例
*/
function callback(entries) {
entries.forEach(entry => {
if (entry?.isIntersecting || entry?.intersectionRatio > 0) {
// 在此处进行图片地址替换
}
});
}
3. 使用 getBoundingClientRect
当你的网站非常注重浏览器的兼容性时,以上的方法就无法满足你的需求,
我们需要使用scroll及其他事件处理程序配合getBoundingClientRect来实现代码降级,
从而确定确定元素是否位于视区中。此方法几乎可以兼容所以浏览器,
但是性能不如intersectionObserve和loading=lazy
// 用于存当前页面已有的图片
observerMap = new Map();
// 通过滚动事件触发 scrollHandle 回调
window.addEventListener('scroll', scrollHandle, true);
// 每次回调里进行遍历已有图片列表
function scrollHandle() {
for (let dom of observerlist) {
// 判断元素是否在可视区从而判断是否替换真实地址
}
}
// 判断元素是否在可视区
function checkDomInView(element) {
const viewwidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const { top, right, bottom, left } = element.getBoundingClientRect();
return left < viewwidth && right > 0 && top < viewHeight && bottom > 0;
}
react实现分装
<script>
//分装函数
const defauleConfig = {
value: "",
inactiveValue: "",
disabled: false,
};
const useInView = (targetRef, config) => {
const {
value = "",
inactiveValue = "",
disabled = false,
} = {
...defauleConfig,
...config,
};
const [inView, setInView] = useState(
typeof IntersectionObserver === "undefined",
);
const [currentValue, setCurrentVale] = useState(
typeof IntersectionObserver === "undefined" ? value : inactiveValue,
);
useEffect(() => {
const target = targetRef.current;
if (!target || typeof IntersectionObserver === "undefined") {
setInView(true);
setCurrentVale(value);
return () => {};
}
const observable = new IntersectionObserver((entries) => {
const { isIntersecting, IntersectionRatio } = entries[0] || {};
if (isIntersecting || IntersectionRatio > 0) {
setCurrentVale(value);
setInView(true);
observable.disconnect();
}
});
observable.observe(target);
return () => {
observable.disconnect();
};
}, [value]);
return [inView, disabled ? value : currentValue];
};
//背景图懒加载组件
const LazyImageContext = createContext({});
const LazyBackground = memo(
forwardRef((props, _ref) => {
const { disabled, style, children, ...resetProps } = props;
const { disabled: _disabled } = useContext(LazyImageContext);
const ref = useRef(_ref);
const inView = useInView(ref);
const finalDisabled =
typeof disabled === "undefined" ? _disabled : disabled;
const _style =
!finalDisabled && !inView ? { backgroundImage: "none" } : {};
return;
<div
{...resetProps}
ref={(el) => (ref.current = el)}
style={{ ...style, ..._style }}
>
{children}
</div>;
}),
);
//图片懒加载组件
const LazyImage = memo(
forwardRef((props, _ref) => {
const {
src,
disabled,
loading = "lazy",
alt = "",
style,
...restProps
} = props;
const { disabled: _disabled } = useContext(LazyImageContext);
const ref = useRef(_ref);
const finalDisabled =
typeof disabled === "undefined" ? _disabled : disabled;
const [, currentSrc] = useInView(ref, {
value: src,
disabled: finalDisabled,
});
const _style = !currentSrc ? { visibility: "hidden" } : {};
return (
<img
{...restProps}
loading={loading}
style={{ ...style, ..._style }}
src={currentSrc}
ref={(el) => (ref.current = el)}
/>
);
}),
);
</script>