本文为稀土AdminJS技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
本文相关demo
本文相关仓库
前言
hello,小伙伴们好呀,我是小羽同学
~
性能优化系列文章又更新啦,这次主要是和小伙伴们来介绍一下懒加载
以及相关的玩法。
懒加载是前端性能优化中很重要的一环,可以有效地降低首页的白屏时间,并且可以广泛地应用到小伙伴们的项目中。
提到懒加载,前端的小伙伴们通常的第一想法就是图片的懒加载啦。
哈哈哈,其实懒加载并不止局限于图片懒加载,它也是有很多的应用场景
。
别着急,咱们一个一个的来介绍。
路由懒加载
使用过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,将真实的图片路径赋值到data-src上。
然后就是监听滚动事件,当前元素距离顶部的高度-clientHeight<=0
的时候,即说明已经进入可视区域
了,这时候咱们就会将img标签中的src路径
修改为data-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的时候,将data-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)}
data-src={item}
src={loadingPath}
/>
))}
</div>
);
}
如下图所示,当图片不在可视区域
内的时候就会显示咱们的loading图片,当到达咱们的可视区域就会加载
真正的图片并且显示出来,从而达到省流
的效果。
基于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>
);
}
模块懒加载
emmm,这个目前的话应该是暂时没有这样的一个定义的,是小羽
基于应用场景这样子称呼。
小伙伴在工作中有没有遇到那种一个页面中有很多个单独的模块
,然后每个模块都会有自己相关的一些渲染
或者请求
的?如果咱们在一开始就将这些模块渲染出来,首先会消耗大量的cpu性能
导致页面初始化
的时候会存在卡顿
的问题,其次如果这些单独的模块涉及到了相关的请求,那么它又会消耗
用户的流量
。总的来说,就是对用户的体验
可能不会很好。
因此,针对这种场景,咱们使用模块的懒加载
。
其实这里使用的模块懒加载,其实就是咱们基于intersectionObserve
r图片懒加载的延伸
使用方案。
小伙伴们仔细想一下,咱们的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请求,这样子可有有效的减少咱们服务端的并发压力
,以及首屏的渲染压力
,即可以减少首页白屏
的时间。
小结
本文小羽和小伙伴们介绍性能优化中的懒加载
,包括了三种
懒加载的方式:路由懒加载
、图片懒加载
、模块懒加载
。并且都分别实现了相关的demo。希望可以在小伙伴们日常的开发中,可以提供到一些帮助。
如果看这篇文章后,感觉有收获的小伙伴们可以点赞
+收藏
哦~
如果想和小羽
交流技术可以加下wx,也欢迎小伙伴们来和小羽唠唠嗑
,嘿嘿~
声明:本文仅供个人学习使用,来源于互联网,本文有改动,本文遵循[BY-NC-SA]协议, 如有侵犯您的权益,请联系本站,本站将在第一时间删除。谢谢你
原文地址:原来懒加载有这些玩法,你确定不看看?