前言
在说懒加载之前先明白href
和src
的区别:
href
标识超文本引用,用在link
和a
等元素上,href
是引用和页面关联,是在当前元素和引用资源之间建立联系。
src
表示引用资源,表示替换当前元素,用在img
,script
,iframe
上,src
是页面内容不可缺少的一部分。
src
是source
的缩写,是指向外部资源的位置,指向的内部会迁入到文档中当前标签所在的位置;在请求src
资源时会将其指向的资源下载并应用到当前文档中,例如js脚本,img图片和frame等元素。
当浏览器解析到这一句的时候会暂停其他资源的下载和处理,直至将该资源加载,编译,执行完毕,图片和框架等元素也是如此,类似于该元素所指向的资源嵌套如当前标签内,这也是为什么要把js放在底部而不是头部。
在项目开发中,我们往往会遇到一个页面需要加载很多图片的情况。我们如果一次性全部加载图片,如果首页图片过大过多,就导致白屏现象严重,所以我们需要对图片加载进行优化,图片懒加载也是比较常见的一种性能优化的方法。
懒加载原理
<img>
标签有一个属性是 src
,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有 src
属性,就不会发送请求。所以大概思路为两个
- 给图片
img
标签自定义一个属性data-src
来存放真实的地址。 - 当滚动页面时,检查所有的
img
标签,判断是否出现在视野中,如果出现在视野中,继续进行判断,看是否被加载过了,如果没有加载,那就进行加载。
那么问题来了如何判断元素是否在可视区域?
这里需要用到三个属性
document.documentElement.clientHeight;//获取可视区域高度
document.documentElement.scrollTop; // 页面向上滚动的高度
img.offsetTop //目标标签相对于document的高度
具体效果是这样的看图
// 判断img是否出现浏览器视野中,并且没有被加载过
function isShow(img) {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const windowHeight = document.documentElement.clientHeight;
const offsetTop = img.offsetTop
return (offsetTop < (windowHeight + scrollTop) && !img.getAttribute('data-loading'))
}
当然涉及到滚动之类的高并发的事件都要进行防抖和节流
然而在这里用那个都显得不尽如意,所以借鉴大神的写法,把两者结合
function throttle(fn, delay) {
// oldTime为上一次触发回调的时间, timer是定时器
let oldTime = 0, timer = null
// 将throttle处理结果当作函数返回
return function () {
// 记录本次触发回调的时间
let nowTime = +new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (nowTime - oldTime < delay) {
// 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
clearTimeout(timer)
timer = setTimeout(function () {
oldTime = nowTime
fn()
}, delay)
} else {
// 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
oldTime = nowTime
fn()
}
}
}
全部代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
img {
width: 400px;
display: block;
margin: 0 auto;
height: 300px;
}
</style>
</head>
<body>
<img data-src="1.jpg" alt="loading">
<img data-src="2.jpg" alt="loading">
<img data-src="3.jpg" alt="loading">
<img data-src="4.jpg" alt="loading">
<img data-src="5.jpg" alt="loading">
<img data-src="6.jpg" alt="loading">
<img data-src="7.jpg" alt="loading">
<img data-src="9.jpg" alt="loading">
<img data-src="10.jpg" alt="loading">
</body>
<script>
const imgs = document.querySelectorAll('img');
const len = imgs.length
function isShow(img) {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const windowHeight = document.documentElement.clientHeight;
const offsetTop = img.offsetTop
//判断当前img是否出现了在视野中。
return offsetTop < (windowHeight + scrollTop)
}
function throttle(fn, delay) {
// oldTime为上一次触发回调的时间, timer是定时器
let oldTime = 0, timer = null
// 将throttle处理结果当作函数返回
return function () {
// 记录本次触发回调的时间
let nowTime = +new Date()
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (nowTime - oldTime < delay) {
// 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
clearTimeout(timer)
timer = setTimeout(function () {
oldTime = nowTime
fn()
}, delay)
} else {
// 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
oldTime = nowTime
fn()
}
}
}
function lazyLoad() {
let count = 0;
//利用闭包来保存一个变量,每次记录更换src的最终位置,这样就不用每次都全部遍历
return () => {
for (var i = count; i < len; i++) {
//如果img到达视野内
if (isShow(imgs[i])) {
imgs[i].src = imgs[i].getAttribute('data-src');
//把img的src换成data-src里面的真实地址,并且记录下最后换到那个位置,
count = i;
}
}
}
}
//用变量来接收lazyLoad运行结果
let lazy = lazyLoad()
//首页加载刚进去需要加载一下
lazy()
window.addEventListener('scroll', throttle(lazy, 300), false)
</script>
</html>