目录
什么是懒加载?
组件级懒加载
方法一:使用Webpack的代码分割能力
方法二:使用Vue异步组件
Vue 2
vue 3
图片懒加载
原理
方法一:lazyload 插件
方法二 :IntersectionObserve()API:IntersectionObserver - Web API 接口参考 | MDN
方法三: loading=“lazy”
方法四:滚动监听+scrollTop+offsetTop+innerHeight
方法五:滚动监听+getBoundingClientRect()
路由懒加载
方法一:ES6推荐方式imprort ()----推荐使用
方法二:webpack 提供的 require.ensure() 实现懒加载
方法三:使用 vue 异步组件 resolve
什么是懒加载?
懒加载,也称为延迟加载,是一种将资源(如图片、组件、代码等)推迟到需要的时候再加载的策略。在 Vue 中,懒加载通常涉及到组件和图片的加载。
组件级懒加载
适合组件懒加载的场景:
- JS 文件体积大的页面:在加载复杂的页面时,将某些组件定义为异步可以显著提高页面的加载速度
- 按需触发的组件:只有在该组件真正需要使用时才去加载相应的资源,从而避免不必要及浪费的加载
- 多页面复用的组件:对于包含大量数据的长列表,您可能希望只在需要滚动到屏幕上的部分时才加载列表
方法一:使用Webpack的代码分割能力
Webpack 在遇到 import() 语法时,会将被导入的模块单独打包成一个文件,只有在需要时才会加载。这有效地减小了初始加载体积,提高了应用的响应速度。
Vue2 和 Vue3 都可以使用,但构建打包后才能生效
const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo');
然后,就可以在需要使用该组件的地方,像使用普通组件一样引入即可:
<template>
<div>
<h1>按需加载示例</h1>
<dialogInfo />
</div>
</template>
方法二:使用Vue异步组件
Vue 2
在 Vue 2 中,可以使用 Vue.component() 方法来注册异步组件,其中异步组件选项是一个返回包含组件信息的对象的函数。具体使用方式如下:
//基本用法:
Vue.component('async-component', () => import('./MyComponent.vue'))
//高阶用法:
const AsyncComponent = () => ({
// 异步方法返回要加载的组件
component: import('./MyComponent.vue'),
// 组件加载期间显示的组件
loading: LoadingComponent,
// 加载出错时显示的组件
error: ErrorComponent,
// 延迟显示加载状态(以毫秒为单位)。默认值:`200`。
delay: 200,
// 等待时间长度等于或超过 `delay`,loadingComponent 将被渲染。如果设置为 0,则始终呈现loadingComponent。
timeout: 3000
});
Vue.component('async-component', AsyncComponent);
在上述示例中,我们使用 Vue.component() 方法将名为 async-component 的异步组件注册到全局组件库中。对于高阶用法,我们定义了一个包装器函数 AsyncComponent(),它返回一个包含组件相关信息的对象。
vue 3
在 Vue 3
中,推荐使用 defineAsyncComponent()
函数来定义异步组件。该函数接受一个参数,即异步组件选项或动态导入方法,并返回一个组件实例。具体使用方式如下:
import { defineAsyncComponent } from 'vue';
//基本用法
const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue'))
//高阶用法
const AsyncComponent = defineAsyncComponent({
// 异步方法返回要加载的组件
loader: () => import('./MyComponent.vue'),
// 组件加载期间显示的组件
loadingComponent: LoadingComponent,
// 加载出错时显示的组件
errorComponent: ErrorComponent,
// 如果设置为 `true`,将使用Suspense包装该组件。默认值:`false`。
suspense: false,
// 延迟显示加载状态(以毫秒为单位)。默认值:`200`。
delay: 200,
// 等待时间长度等于或超过 `delay`,loadingComponent 将被渲染。如果设置为 0,则始终呈现loadingComponent。
timeout: 3000,
})
在上述示例中,我们使用 defineAsyncComponent() 函数将名为 AsyncComponent 的异步组件定义为一个返回加载组件的函数。与 Vue 2 不同的是,在 Vue 3 中异步组件选项可以是一个对象,而不仅仅是一个返回对象的函数。
在 Vue 3 中使用 defineAsyncComponent() 定义包装了 Suspense 的异步组件的示例代码:
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent({
loader: () => import('./MyComponent.vue'),
suspense: true,
delay: 200
});
// 在模板中使用异步组件
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<!-- 加载中的提示 -->
</template>
</Suspense>
</template>
无论是
Vue2
还是Vue 3
,一旦异步组件注册成功,就可以像普通组件一样进行使用
图片懒加载
原理
存储图片的真实路径,把图片的真实路径绑定给一个以data开头的自定义属性data-src即可,页面中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片(没有请求就提高了性能)
<div class="scrollLoading" data-src="loaded.html">加载中...</div>
设置img的默认src为其他图片(所有的img都用这一张,只会发送一次请求
<img data-src="xxx" src="1px.gif" width="180" height="180"/>
需要一个滚动事件,判断元素是否在浏览器窗口,一旦进入视口才进行加载,当滚动加载的时候,就把这张图片替换为真正的url地址(也就是data-src里保存的值)
等到图片进入视口后,利用js提取data-src的真实图片地址赋值给src属性,就会去发送请求加载图片,真正实现了按需加载
实现图片懒加载的多种方法的区别就在于判断元素是否进入视口的方式不同,当使用方法四和方法五时,如果发生滚动事件,会产生大量的循环和判断操作判断图片是否可视区里,因此需要用节流进行优化。
方法一:lazyload 插件
安装
// vue 2 安装
npm i vue-lazyload@1.2.3 -S
// vue 3 安装
npm i vue-lazyload-next -S
导入并注册
// vue 2 导入
import VueLazyload from 'vue-lazyload'
// vue 3 导入
import VueLazyloadNext from 'vue-lazyload-next';
// vue 2 注册
Vue.use(VueLazyload, {
//参数配置 可不填
// 懒加载默认加载图片
loading: 'xxx.png',
// 加载失败后加载的图片
error: 'xxx.png',
preLoad: 1.3, // 预加载高度的比例
attempt: 3 // 尝试加载次数
})
// vue 3 注册
app.use(VueLazyloadNext, {
// 添加一些配置参数 可不填
// 懒加载默认加载图片
loading: 'xxx.png',
// 加载失败后加载的图片
error: 'xxx.png',
preLoad: 1.3, // 预加载高度的比例
attempt: 3 // 尝试加载次数
});
vue-lazyload 提供了一个自定义指令 v-lazy
,可以在img标签或者任何需要设置背景图片的标签上使用它。例如
<!-- 懒加载img标签 -->
<img v-lazy="imgUrl" />
<!-- 懒加载背景图片 -->
<div v-lazy:background-image="bgUrl"></div>
v-lazy 指令接收一个字符串类型的值,表示图片的地址。如果是背景图片,需要在指令后加上 :background-image 修饰符(注意冒号)。
当页面滚动时,vue-lazyload 会检测元素是否进入可视区域,如果是,则替换元素的 src 或者 style 属性,从而实现懒加载。
注意:若图片为循环渲染、分页显示,则必须写 key 值,不然切换页面后页面视图不刷新
方法二 :IntersectionObserve()
API:IntersectionObserver - Web API 接口参考 | MDN
原理
IntersectionObserver 是是浏览器原生提供的构造函数,使用它能省到大量的循环和判断。这个构造函数的作用是它能够观察可视窗口与目标元素产生的交叉区域。简单来说就是当用它观察我们的图片时,当图片出现或者消失在可视窗口,它都能知道并且会执行一个特殊的回调函数,就可以利用这个回调函数实现需要的操作。
vue3 中选择用 hook 进行集成
// useLazyload.ts文件
// 定义自定义指令
const defineDirective = (app: any) => {
app.directive('lazy', {
mounted(el: HTMLImageElement, bindings: any) {
// el表示使用指令的DOM元素
// 指令的功能:实现图片的懒加载
// 监听图片是否进入可视区
const observer = new IntersectionObserver(([{ isIntersecting }]) => {
// true;进入可视区域,false:未进入可视区域
if (isIntersecting) {
// 给图片的src属性赋值图片的地址
el.src = bindings.value;
// 取消图片的监听,默认是会一直监听的,如果不取消。就会一直执行
// eslint-disable-next-line spellcheck/spell-checker
observer.unobserve(el);
}
});
// 监听dom元素
observer.observe(el);
}
});
};
export default {
install(app: any) {
// 自定义指令
defineDirective(app);
}
};
基本用法
// main.ts
import lazy from './hooks/useLazy';
app.use(lazy);
对需要进行懒加载的 img 标签,直接将 :src 替换为 v-lazy
<img
class="image"
:key="data.rendered_image_url"
v-lazy="data.rendered_image_url"
/>
注意:若图片为循环渲染、分页显示,则必须写key值,不然切换页面后页面视图不刷新
方法三: loading=“lazy”
HTML新增 loading属性
基本用法
<img src="xxx.png" loading="lazy">
loading 属性支持 3 种属性值:
- auto 浏览器默认的懒加载策略,和不增加这个属性的表现一样
- lazy 在资源距当前视窗到了特定距离内后再开始加载
- eager 立即加载,无论资源在页面中什么位置
图片必须声明 width 和 height,不然会看到布局发生移动
兼容性:Lazy loading via attribute for images & iframes - LS
方法四:滚动监听+scrollTop+offsetTop+innerHeight
如何判断元素是否到达可视区域
- window.innerHeight 是浏览器可视区的高度;
- document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的距离;
- imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离);
- 内容达到显示区域: img.offsetTop < window.innerHeight + document.body.scrollTop;
- 到达可视区域后,imgs[i].src = imgs[i].getAttribute('data-src') as string;将 data-src 属性值赋值给 src,实现懒加载
但在集成过程中发现,由于 img 父元素标签没有并且无法设置高度,scrollTop 始终为0,无法实现动态计算 window.innerHeight + document.body.scrollTop 的值
方法五:滚动监听+getBoundingClientRect()
API:Element.getBoundingClientRect()
//获取所有img标签
const imgs = document.getElementsByTagName('img');
onMounted(() => {
//用于首屏加载
lazyLoad();
//添加滚动事件监听
document.addEventListener('scroll', throttle(lazyLoad, 500), true);
});
onUnMounted(() => {
document.removeEventListener('scroll', throttle(lazyLoad, 500), true);
});
// 节流
const throttle = (fn: { apply: (arg0: any, arg1: any[]) => void }, t: number) => {
let flag = true;
const interval = t || 500;
return function (this: any, ...args: any) {
if (flag) {
fn.apply(this, args);
flag = false;
setTimeout(() => {
flag = true;
}, interval);
}
};
};
const lazyLoad = () => {
const offsetHeight = window.innerHeight || document.documentElement.clientHeight;
Array.from(imgs).forEach(item => {
const oBounding = item.getBoundingClientRect(); //返回一个矩形对象,包含上下左右的偏移值
if (0 <= oBounding.top && oBounding.top <= offsetHeight) {
// //性能优化 进行判断 已经加载的不会再进行加载
if (item.getAttribute('alt') !== 'loaded') {
item.setAttribute('src', item.getAttribute('data-src') as string);
item.setAttribute('alt', 'loaded');
}
}
});
};
缺点
- 挂载时需要立刻调用 lazyLoad 函数,不然首屏不会加载
- 若需要图片分页,切换分页后不会也主动加载,需要调用 lazyLoad 方法
- 同样需要给图片设置 key 值,不然分页图片不刷新
路由懒加载
vue 在项目打包/构建(一般是npm run build 具体看package.json文件)之后,会生成一个 dist 文件夹。在 dist 文件夹里面又有一个 js/app.js 文件,这里主要存放的是整个项目的业务逻辑代码。随着项目不断的开发迭代,业务逻辑越来越多,app.js 文件也会越来越大。在线上就会容易出现进入首页时所需时间过长或者出现白屏的问题
这会是影响加载时间的重要因数。当构建的项目比较大的时候,懒加载可以分割代码块,提高页面的初始加载效率解决白屏问题。
方法一:ES6推荐方式imprort ()----推荐使用
// 官网可知:下面没有指定webpackChunkName,每个组件打包成一个js文件。
const Foo = () => import('../components/Foo')
const Aoo = () => import('../components/Aoo')
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。
const Foo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Foo')
const Aoo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Aoo')
方法二:webpack 提供的 require.ensure() 实现懒加载
require.ensure 可实现按需加载资源,包括 js/css 等。他会给里面 require 的文件单独打包,不会和主文件打包在一起。多个路由指定相同的 chunkName,会合并打包成一个 js 文件。
- 第一个参数是数组,表明第二个参数里需要依赖的模块,这些会提前加载。
- 第二个是回调函数,在这个回调函数里面 require 的文件会被单独打包成一个 chunk,不会和主文件打包在一 起,这样就生成了两个 chunk,第一次加载时只加载主文件。
- 第三个参数是错误回调。
- 第四个参数是单独打包的 chunk 的文件名
import Vue from 'vue';
import Router from 'vue-router';
Vue.use('Router')
export default new Router({
routes:[{
{path:'./',
name:'HelloWorld',
component: r => require.ensure([], () => r(require('@/components/home')), 'demo')
}
}]
})
方法三:使用 vue 异步组件 resolve
它主要是使用了 resolve 的异步机制,用 require 代替了 import,实现按需加载
//const 组件名 = resolve => require([‘组件路径'],resolve)
//(这种情况下一个组件生成一个js文件)
const home = resolve => require(['../view/home'],resolve)
require 与 import 详解