本文在github做了收录 github.com/Michael-lzg…
demo源码地址 github.com/Michael-lzg…
在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。
我们先来看一下页面启动时加载的图片信息。
如图所示,这个页面启动时加载了几十张图片(甚至更多),而这些图片请求几乎是并发的,在 Chrome 浏览器,最多支持的并发请求次数是有限的,其他的请求会推入到队列中等待或者停滞不前,直到上轮请求完成后新的请求才会发出。所以相当一部分图片资源请求是需要排队等待时间的。
在上面可以看出,有部分图片达到几百 kB,设置 2M(这锅必须运营背,非得上传高清大图不可?),直接导致了加载时间过长
针对以上情况,进行图片懒加载有以下优点:
图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的
我们先来看下页面结构
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Lazyload</title>
<style> img {
display: block;
margin-bottom: 50px;
height: 200px;
width: 400px;
} </style>
</head>
<body>
<img src="./img/default.png" data-src="./img/1.jpg" />
<img src="./img/default.png" data-src="./img/2.jpg" />
<img src="./img/default.png" data-src="./img/3.jpg" />
<img src="./img/default.png" data-src="./img/4.jpg" />
<img src="./img/default.png" data-src="./img/5.jpg" />
<img src="./img/default.png" data-src="./img/6.jpg" />
<img src="./img/default.png" data-src="./img/7.jpg" />
<img src="./img/default.png" data-src="./img/8.jpg" />
<img src="./img/default.png" data-src="./img/9.jpg" />
<img src="./img/default.png" data-src="./img/10.jpg" />
</body>
</html>
先获取所有图片的 dom,通过 document.body.clientHeight 获取可视区高度,再使用 element.getBoundingClientRect() api 直接得到元素相对浏览的 top 值, 遍历每个图片判断当前图片是否到了可视区范围内。代码如下:
function lazyload() {
let viewHeight = document.body.clientHeight //获取可视区高度
let imgs = document.querySelectorAll('img[data-src]')
imgs.forEach((item, index) => {
if (item.dataset.src === '') return
// 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
let rect = item.getBoundingClientRect()
if (rect.bottom >= 0 && rect.top < viewHeight) {
item.src = item.dataset.src
item.removeAttribute('data-src')
}
})
}
最后给 window 绑定 onscroll 事件
window.addEventListener('scroll', lazyload)
主要就完成了一个图片懒加载的操作了。但是这样存在较大的性能问题,因为 scroll 事件会在很短的时间内触发很多次,严重影响页面性能,为了提高网页性能,我们需要一个节流函数来控制函数的多次触发,在一段时间内(如 200ms)只执行一次回调。
下面实现一个节流函数
function throttle(fn, delay) {
let timer
let prevTime
return function (...args) {
const currTime = Date.now()
const context = this
if (!prevTime) prevTime = currTime
clearTimeout(timer)
if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
clearTimeout(timer)
return
}
timer = setTimeout(function () {
prevTime = Date.now()
timer = null
fn.apply(context, args)
}, delay)
}
}
然后修改一下 srcoll 事件
window.addEventListener('scroll', throttle(lazyload, 200))
通过上面例子的实现,我们要实现懒加载都需要去监听 scroll 事件,尽管我们可以通过函数节流的方式来阻止高频率的执行函数,但是我们还是需要去计算 scrollTop,offsetHeight 等属性,有没有简单的不需要计算这些属性的方式呢,答案就是 IntersectionObserver。
IntersectionObserver 是一个新的 API,可以自动"观察"元素是否可见,Chrome 51+ 已经支持。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。我们来看一下它的用法:
var io = new IntersectionObserver(callback, option)
// 开始观察
io.observe(document.getElementById('example'))
// 停止观察
io.unobserve(element)
// 关闭观察器
io.disconnect()
IntersectionObserver 是浏览器原生提供的构造函数,接受两个参数:callback 是可见性变化时的回调函数,option 是配置对象(该参数可选)。
目标元素的可见性变化时,就会调用观察器的回调函数 callback。callback 一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
var io = new IntersectionObserver((entries) => {
console.log(entries)
})
callback 函数的参数(entries)是一个数组,每个成员都是一个 IntersectionObserverEntry 对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries 数组就会有两个成员。
下面我们用 IntersectionObserver 实现图片懒加载
const imgs = document.querySelectorAll('img[data-src]')
const config = {
rootMargin: '0px',
threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let img = entry.target
let src = img.dataset.src
if (src) {
img.src = src
img.removeAttribute('data-src')
}
// 解除观察
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {
observer.observe(image)
})
vue 中除了平时常用的 v-show、v-bind、v-for 等指令外,还可以自定义指令。Vue 指令定义函数提供了几个钩子函数(可选):
实现一个懒加载指令的思路
代码如下
新建 LazyLoad.js 文件
const LazyLoad = {
// install方法
install(Vue, options) {
const defaultSrc = options.default
Vue.directive('lazy', {
bind(el, binding) {
LazyLoad.init(el, binding.value, defaultSrc)
},
inserted(el) {
if (IntersectionObserver) {
LazyLoad.observe(el)
} else {
LazyLoad.listenerScroll(el)
}
},
})
},
// 初始化
init(el, val, def) {
el.setAttribute('data-src', val)
el.setAttribute('src', def)
},
// 利用IntersectionObserver监听el
observe(el) {
var io = new IntersectionObserver((entries) => {
const realSrc = el.dataset.src
if (entries[0].isIntersecting) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
})
io.observe(el)
},
// 监听scroll事件
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll', () => {
handler(el)
})
},
// 加载真实图片
load(el) {
const windowHeight = document.documentElement.clientHeight
const elTop = el.getBoundingClientRect().top
const elBtm = el.getBoundingClientRect().bottom
const realSrc = el.dataset.src
if (elTop - windowHeight < 0 && elBtm > 0) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
},
// 节流
throttle(fn, delay) {
let timer
let prevTime
return function (...args) {
const currTime = Date.now()
const context = this
if (!prevTime) prevTime = currTime
clearTimeout(timer)
if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
clearTimeout(timer)
return
}
timer = setTimeout(function () {
prevTime = Date.now()
timer = null
fn.apply(context, args)
}, delay)
}
},
}
export default LazyLoad
在 main.js 里 use 指令
import LazyLoad from './LazyLoad.js'
Vue.use(LazyLoad, {
default: 'xxx.png',
})
将组件内 <img> 标签的 src 换成 v-lazy
<img v-lazy="xxx.jpg" />
这样就能完成一个 vue 懒加载的指令了。
当滚动条滚动到底部时,加载新数据;需要根据距离底部的位置加载数据,考虑到新数据进来会使滚动条距离底部的位置有所改变,避免重复加载数据
什么是懒加载?懒加载也叫延迟加载,即在需要的时候进行加载,随用随载。在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多
安装插件npm install vue-lazyload --save;在main.js文件中注册插件。配置参数:preLoad提前加载高度(数字 1 表示 1 屏的高度)、error图片加载失败时显示的图片
在用到el-tree的懒加载和默认勾选功能时,若第一次勾选前几个连续节点,第二次进入默认勾选时,由于el-tree子节点尚未完全加载(只加载出来前几个),默认勾选已经开始
当我们向下滚动的时候图片资源才被请求到,这也就是我们本次要实现的效果,进入页面的时候,只请求可视区域的图片资源这也就是懒加载。比如我们加载一个页面,这个页面很长很长,长到我们的浏览器可视区域装不下
如今为了提升应用性能,懒加载被广泛使用于 Web 应用中。它帮助开发者减少网站加载时间,节省流量以及提升用户体验。但懒加载的过度使用会给应用性能带来负面影响
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!