图片懒加载是为了减少资源的负担,在加载首屏图片时减少浏览器负担,加快首屏加载速度。之前在加载图片列表的时候总是图省事直接用element-ui的图片组件,今天好好的了解了图片懒加载的原理,具体如下:
一、懒加载原理
- 判断图片是否进入可视区域范围内
- 图片进入可视区域后触发图片加载
- 在判断图片是否进入可视区域范围内主要是判断可视区域的高度以及图片距离可视区域顶部的高度。
- 当前可视区域的高度 - 元素距离可视区域顶部的高度 > 0 说明图片进入可视区域,此刻出发加载数据事件
二、原生代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载</title>
<style>
.lazy-img-list {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: space-between;
}
.lazy-img-list img {
width: 49%;
height: 180px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="lazy-img-list">
<img src="./img/1.jpg" alt="" data-src="./img/1.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/1.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
</div>
<script>
const imgs = document.getElementsByTagName('img')
// 获取可视区域的高度
const viewHeight = document.documentElement.clientHeight
// 统计当前加载到哪张图片,避免每一次都从第一张照片开始检查
let num = 0
// 当前可视区域的高度 - 元素距离可视区域顶部的高度 > 0 说明元素进入可视区域
function lazyLoad() {
for (let i = 0; i < imgs.length; i++) {
const item = imgs[i]
let distance = viewHeight - item.getBoundingClientRect().top
if (distance > 0) {
item.src = item.getAttribute("data-src")
num = i + 1
}
}
}
// 节流函数
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)
}
}
// 监听scroll事件
window.addEventListener("scroll", throttle(lazyLoad,200))
lazyLoad()
</script>
</body>
</html>
三、IntersectionObserver的使用
- 原理
IntersectionObserver 是一个新的API,可以自动监听元素是否进入了设备的可视区域,而不需要频繁的计算去做判断。对于图片懒加载来说,需要获取可视化区域高度计算高度差才可以来触发图片加载。而IntersectionObserver这个方法可直接判断(观察)。
IntersectionObserver用法 - 使用场景
1)图片懒加载——图片滚动到可见时再加载
2)内容无限滚动——用户滚动到接近内容底部时直接加载更多,无需用户操作翻页,给用户一种网页可以无限滚动的错觉。
3)检测广告的曝光情况——为了计算广告收益,需要知道广告元素的曝光情况
4)在用户看见某个区域时执行任务或播放动画 - 具体使用方法
const target = document.getElementById("dom")
const io = new IntersectionObserver((changes, observer)=>{
console.log('changes-------',changes);
console.log('observer-------',observer);
})
// observe方法
// 开始观察 —— 用来指定被监听的目标元素
io.observe(target)
// 停止观察 —— 指定停止监听的目标元素
io.unobserve(target)
// 关闭观察 —— 不需要接收参数
io.disconnect()
// 返回所有被观察到对象,返回值是一个数组
const observerList = io.takeRecords()
console.log('被观察对象------',observerList);
- 用IntersectionObserver实现图片懒加载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intersection Observer用法</title>
<style>
.lazy-img-list {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: space-between;
}
.lazy-img-list img {
width: 49%;
height: 180px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="dom" class="lazy-img-list">
<img src="./img/1.jpg" alt="" data-src="./img/1.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/1.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/4.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/2.jpg">
<img src="./img/1.jpg" alt="" data-src="./img/3.jpg">
</div>
<script>
// 图片懒加载
// 获取图片列表
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)
})
</script>
</body>
</html>
- 在vue中实现图片懒加载
1) 首先先写一个页面,展示图片列表
<template>
<div class="lazy-div">
<p>图片加载</p>
<div class="lazy-img-list">
<img v-for="(item,index) in imgList" :key="index" v-lazy="item.src">
</div>
</div>
</template>
<script>
export default {
name: 'LazyLoad',
data() {
return {
imgList: [
{
key: 1,
src: require('../../assets/images/1.jpg')
},
{
key: 2,
src: require('../../assets/images/2.jpg')
},
{
key: 3,
src: require('../../assets/images/3.jpg')
},
{
key: 4,
src: require('../../assets/images/4.jpg')
},
{
key: 1,
src: require('../../assets/images/1.jpg')
},
{
key: 2,
src: require('../../assets/images/2.jpg')
},
{
key: 3,
src: require('../../assets/images/3.jpg')
},
{
key: 4,
src: require('../../assets/images/4.jpg')
},
{
key: 1,
src: require('../../assets/images/1.jpg')
},
{
key: 2,
src: require('../../assets/images/2.jpg')
},
{
key: 3,
src: require('../../assets/images/3.jpg')
},
{
key: 4,
src: require('../../assets/images/4.jpg')
},
{
key: 1,
src: require('../../assets/images/1.jpg')
},
{
key: 2,
src: require('../../assets/images/2.jpg')
},
{
key: 3,
src: require('../../assets/images/3.jpg')
},
{
key: 4,
src: require('../../assets/images/4.jpg')
},
{
key: 1,
src: require('../../assets/images/1.jpg')
},
{
key: 2,
src: require('../../assets/images/2.jpg')
},
{
key: 3,
src: require('../../assets/images/3.jpg')
},
{
key: 4,
src: require('../../assets/images/4.jpg')
}
]
}
}
}
</script>
<style scoped>
.lazy-img-list {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: space-between;
}
.lazy-img-list img {
width: 49%;
height: 180px;
margin-bottom: 10px;
}
</style>
2) 创建一个LazyLoad.js,暴露出一个install方法,并在main.js 引入,便于在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(根据自己组件的实际位置引入)
import LazyLoad from "../src/views/lazyLoad/LazyLoad"
Vue.use(LazyLoad)