原来懒加载有这些玩法,你确定不看看?
路由懒加载
使用过react/vue等框架的小伙伴们,或多或少都有使用过这个东西吧?
特别是使用了vite的小伙伴们,如果不做路由的懒加载,按vite的逻辑,会将每一个tsx、less文件都加载出来,从而导致第一次加载的时间变得超级久的。
路由懒加载的作用就是将咱们的路由模块,剥离出来成为一个个单独的js和css文件,当你需要使用到他的时候,再将相关的文件加载并渲染出来。
这里以react为例子,主要是使用了react中的lazy和Suspense。
lazy和Suspense配合使用,可以显著减少主包的体积,加快加载速度,从而提升用户体验。当路由切换的时候才会加载lazy中的代码。而代码的加载是一个异步的过程,所以当代码没有完成加载的时候则会显示fallback中的内容,一般是一个loading组件,用于告诉用户正在加载中。当加载完成了,就会显示lazy的内容。
import React, { lazy, Suspense } from 'react';
import { useRoutes, Navigate } from 'react-router-dom';
const LazyBrowser = lazy(() => import('@/pages/LazyBrowser'));
const LazyIntersectionObserver = lazy(
() => import('@/pages/LazyIntersectionObserver'),
);
const LazyScroll = lazy(() => import('@/pages/LazyScroll'));
export default function Router() {
let element = useRoutes([
{
path: '/lazy-browser',
element: <LazyBrowser />,
children: [],
},
{
path: '/lazy-intersection-observer',
element: <LazyIntersectionObserver />,
children: [],
},
{
path: '/lazy-scroll',
element: <LazyScroll />,
children: [],
}
]);
return <Suspense fallback={<div>loading...</div>}>{element}</Suspense>;
}
图片懒加载
基于浏览器特性的图片懒加载
如果你不想引入任何的库,又不想写太多的代码的话,这可能是最适合你的方案了。
只需要在你的image标签中添加一个loading="lazy"
即可
import React from 'react';
import { imageList } from '@/utils/imageList';
import './index.less';
export default function LazyBrowser() {
return (
<div className='lazy-browser'>
{imageList.map((item) => (
<div className='image' key={item}>
<img loading='lazy' src={item} />
</div>
))}
</div>
);
}
好了,最简单的实现方案已经搞定了。但是这个方案还是会还有一些缺点,比如无法设置默认的加载图片、加载失败的图片。
基于滚动事件的图片懒加载
有理想的小伙们,对此又采用其他方案,从而实现了这些功能。
第一种是通过监听滚动事件,判断高度与图片的位置,从而实现图片懒加载。
这里小伙伴们需要先熟悉三个滚动相关的参数:offsetTop、clientHeight、scrollTop
offsetTop:当前元素顶部距离最近父元素顶部的距离,和有没有滚动条没有关系。 clientHeight:当前元素的高度,包括padding但不包括border、水平滚动条、margin的元素的高度。 scrollTop:代表在有滚动条时,滚动条向下滚动的距离也就是元素顶部被遮住部分的高度。在没有滚动条时scrollTop===0。 首先先将loading的图片路径赋给每个img标签中的src,将真实的图片路径赋值到src上。
然后就是监听滚动事件,当前元素距离顶部的高度-clientHeight<=0的时候,即说明已经进入可视区域了,这时候咱们就会将img标签中的src路径修改为src中的真实图片路径。
import React, { useEffect, useRef } from 'react';
import { imageList } from '@/utils/imageList';
import './index.less';
const loadingPath = location.href + '/images/loading.gif';
export default function LazyScroll() {
const domRef = useRef([]);
const lazyScrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
getTop();
lazyScrollRef.current.addEventListener('scroll', getTop);
return () => {
if (lazyScrollRef.current) {
lazyScrollRef.current.removeEventListener('scroll', getTop);
}
};
}, []);
const getTop = () => {
// 当前视窗的可视区域
let clientHeight = lazyScrollRef.current.clientHeight;
let len = domRef.current.length;
for (let i = 0; i < len; i++) {
// 元素距离页面顶部的距离
let { top } = domRef.current[i].getBoundingClientRect();
// 当图片减去可视区域高度小于等于0的时候,将src的值赋值给src
if (top - clientHeight <= 0) {
if (domRef.current[i].src === loadingPath) {
domRef.current[i].src = domRef.current[i].dataset.src;
}
}
}
};
return (
<div className='lazy-scroll' ref={lazyScrollRef}>
{imageList.map((item, index) => (
<img
className='image'
key={item}
ref={(e) => (domRef.current[index] = e)}
src={item}
src={loadingPath}
/>
))}
</div>
);
}
基于intersectionObserver的图片懒加载
咱们可以通过监听scroll事件,判断图片是否在可视区域的方式外,从而实现图片懒加载。
但是这样子做的话,其实咱们是饶了一大圈来实现图片懒加载,那有没有什么东西可以直接判断图片是否在可是区域内的呢?
答案就是intersectionObserver。
但是这个api在旧的浏览器上有一定的兼容性问题,如can i use中所示,如果需要兼容ie浏览器的小伙伴可以移步了,如果只需要兼容新版本的浏览器的小伙伴可以放心食用。
ok,那么咱们就基于intersectionObserver简单的封装一个自定义的hooks吧,这个hooks的作用主要是会监听咱们的dom节点,如果未在可视区域的时候会返回false,如果在可视区域则会返回true,并且当第一次出现在可视区域的时候会清除监听,然后在销毁的时候也是需要记得清除一下监听哦。
import { useState, useEffect, useRef, useMemo } from 'react';
const useIntersectionObserver = (domRef: any) => {
const [visible, setVisible] = useState(false);
const intersectionObserver = useMemo(
() =>
new IntersectionObserver(
(
entries: IntersectionObserverEntry[],
observer: IntersectionObserver,
) => {
entries.map((item) => {
if (item.isIntersecting) {
setVisible(true);
observer.disconnect();
}
});
},
),
[],
);
useEffect(() => {
if (domRef.current) {
intersectionObserver.observe(domRef.current);
}
}, [domRef.current]);
useEffect(() => {
return () => {
// 清除监听
intersectionObserver.disconnect();
};
}, []);
return visible;
};
export default useIntersectionObserver;
当咱们把这个hooks封装好了,你会发现原来图片懒加载如此简单。只需要往useIntersectionObserver中传入你的dom节点,根据返回值是false 或者 true,分别显示loading和真实的图片即可。
import React, { useRef } from 'react';
import { imageList } from '@/utils/imageList';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import './index.less';
const loadingPath = location.origin + '/images/loading.gif';
const Item = ({ url }) => {
const itemRef = useRef<HTMLDivElement>();
const visible = useIntersectionObserver(itemRef);
return (
<div className='image' ref={itemRef}>
{visible ? <img src={url} /> : <img src={loadingPath} />}
</div>
);
};
export default function LazyIntersecctionObserver() {
return (
<div className='lazy-intersection-observer'>
{imageList.map((item) => (
<Item url={item} key={item} />
))}
</div>
);
}
模块懒加载
小伙伴在工作中有没有遇到那种一个页面中有很多个单独的模块,然后每个模块都会有自己相关的一些渲染或者请求的?如果咱们在一开始就将这些模块渲染出来,首先会消耗大量的cpu性能导致页面初始化的时候会存在卡顿的问题,其次如果这些单独的模块涉及到了相关的请求,那么它又会消耗用户的流量。总的来说,就是对用户的体验可能不会很好。
因此,针对这种场景,咱们使用模块的懒加载。
其实这里使用的模块懒加载,其实就是咱们基于intersectionObserver图片懒加载的延伸使用方案。
小伙伴们仔细想一下,咱们的useIntersectionObserver这个自定义的hooks里做了些什么?
这个hooks会监听咱们传入的dom,然后当这个dom元素在可视区域的时候会返回true。如果咱们在true的时候将图片替换成咱们相关模块,这不就是模块化的懒加载了吗?
使用的方式一致
import React, { useEffect, useState, useRef } from 'react';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import axios from 'axios';
import { Spin } from 'antd';
export default function LazyModuleItem() {
const itemRef = useRef(null);
const visible = useIntersectionObserver(itemRef);
const [loading, setLoading] = useState(true);
const [data, setData] = useState('');
const init = () => {
setLoading(true);
axios
.get('https://api.uomg.com/api/comments.163?format=text')
.then((res: any) => {
setLoading(false);
console.log(res);
setData(res.data);
});
};
useEffect(() => {
if (visible) {
init();
}
}, [visible]);
return (
<div ref={itemRef}>
{!visible || loading ? <Spin /> : <div>{data}</div>}
</div>
);
}
实现效果如下图,咱们可以发现,在咱们不断滚动,使得模块进入咱们的视野,此时才会发起新的api请求,这样子可有有效的减少咱们服务端的并发压力,以及首屏的渲染压力,即可以减少首页白屏的时间。