当一个单页面应用加载发送的请求很多时,页面可能会出来的很慢影响用户体验,此时我们就可以对项目进行优化,使用数据懒加载以及图片懒加载优化网络性能。懒加载的原理是当组件对象或者Dom对象出现在可视区域的时候再发送请求,这样可以大大减轻网络压力使得页面加载更快。
1. 图片懒加载
(1)图片懒加载的原理
当一个页面有非常多的图片时,img标签的src属性会向服务器发送大量请求获取图片资源此时就会造成比较大的网络压力。图片懒加载的原理是:开始时,不设置src属性,当图片
可见
时再给img标签的src属性赋值,去加载图片。此处使用用到了自定义指令配合Vue.use()进行注册。
(2)如何判断元素是可见的
在之前是使用scrollTop、offset、height等来计算可见区域
现在:浏览器有一个内置的api,
IntersectionObserver
可以用来监听元素是否可见
但是在开发中,一般使用vueuse库中的
useIntersectionObserver
这个api,
使用自定义指令配合app.use做成独立的插件。
这里的数据、图片懒加载均使用到了第三方包@vueuse/core,这是一个功能非常强大的第三方包。以在Vue3项目中的实现举例:
安装@vueuse/core
yarn add @vueuse/core
在src中创建 src/xxx/index.ts
// 自定义指令
import { useIntersectionObserver } from '@vueuse/core'
// 默认图片
import defaultImg from '../assets/images/200.png'
import { App } from 'vue'
export default {
install(app: App) {
// 全局指令
app.directive('lazy', {
// mounted是v3中自定义指令的生命周期,它会被自动调用
// 它表示的涵义和组件中的mounted是一致的
// el是dom元素,binding:通过binding.value可以取出传来的值
mounted(el, binding) {
// 设置默认图
el.src = defaultImg
// console.log('lazy', el, binding.value);
// 实时监听el是否可见,如果可见,给他的src设置binding.value
const {stop} = useIntersectionObserver(el, ([{isIntersecting}]) => {
if(isIntersecting) {
el.src = binding.value
// 停止监听
stop()
}
})
// 如果图片加载失败,显示默认的图片
el.onerror = () => {
el.src = defaultImg
}
}
})
}
}
在main.ts中注册
import { createApp } from 'vue'
import App from './App.vue'
import xxx from '@/xxx/index'
createApp(App).use(xxx).mount('#app')
2. 数据懒加载
场景:当主页又很多个模块,每个模块都发ajax请求拿数据,并且是在组件创建时发送请求,这样有一个后果:靠近底层的模块其实用户看不到,这个发请求有点浪费。
思路:判断当前模块是否可见,可见就发送请求。
做法:封装一个自定义的组件,接受一个回调,返回一个ref,这个ref可以用在模块的外层dom上:当这个ref引用的dom可见时,就执行这个回调(发请求)
实操:使用vueuse中的useIntersectionObserver方法去封装。(react生态中,有一个和vueuse类似的库:ahooks)
给组件对象或Dom对象添加ref属性
<demo ref=’target’><demo>
封装组件以便服用
import { useIntersectionObserver } from '@vueuse/core';
import { onMounted, ref } from 'vue';
// 接收一个回调函数,返回一个ref引用
// 功能:当ref引用的dom处于可见状态时,调用一次回调函数
export function useLazyDate( fn:() => void ) {
// 当整个区块处于可见状态时,才发ajax
const target = ref(null)
onMounted(() => {
const {stop} = useIntersectionObserver(target.value, ([{isIntersecting}]) => {
if(isIntersecting) {
fn()
// 停止监听
stop()
}
})
})
return target
}