图片懒加载
图片懒加载
之前学习vue2的时候,我了解到图片懒加载,只是当时没花时间去解决,仅仅是使用了vant2提供的懒加载插件,现在想尝试使用JS实现。
什么是懒加载?其实就是在页面渲染时,不是一次性加载全部的图片资源,针对图片元素只在视口范围内按需加载。可以节省加载时间和优化性能,这个功能在首屏加载中有很大的功效。下面给出对比图:
没使用懒加载的:
使用了懒加载的:
由此可见性能确实大幅度优化了
大致思路其实就是:在图片元素身上先不使用src属性,使用自定义属性data-src(名字可以随便取,格式为data-xxx)保存图片的路径,通过判断图片是否进入可见视口来讲data-src的值给src,使得图片可以显示。html代码如下:
<img data-src="./img/h1.png" alt="">
<img data-src="./img/h2.png" alt="">
<img data-src="./img/h3.png" alt="">
<img data-src="./img/h4.png" alt="">
<img data-src="./img/h5.png" alt="">
<img data-src="./img/h6.png" alt="">
先将一些常用的属性介绍一下:
属性 | 作用 | 说明 |
---|---|---|
scrollLeft和scrollTop | 被卷去的左侧和头部距离 | 配合页面滚动,这是可读写的 |
offsetLeft和offsetTop | 获取元素距离自己定位父级元素的左上距离 | 获取元素时使用,这是只读的 |
clientWidth和clientHeight | 获取元素的宽高 | 不包含border,margin和滚动条,获取元素大小,这是只读的 |
offsetWidth和offsetHeight | 获取元素的宽高 | 包含border,padding和滚动条等,这是只读的 |
方法1:整体距离比较
总体思路是:通过比较页面卷去的高度、图片距离文档页面顶部的高度和浏览器视口的高度,如果卷去的高度与视口高度之和大于图片距离页面顶部的高度,就应该将图片加载出来,如下图:
- offsetTop获取的是距离父元素的高度,此处父元素是body,所以可以使用offsetTop获取距离整个页面文档的高度
- document.documentElement指的是html
function lazyLoad() {
const imgs = document.querySelectorAll('img')
const clientHeight = document.documentElement.clientHeight
const scrollTop = document.documentElement.scrollTop
//n是第0张图片,为了减少for循环次数,对于前面的图片将不再进行判断操作
let n=0
for (let i = n; i < imgs.length; i++) {
//不仅仅是高度的判断,同时自身属性如果已经有src属性,就不需要在进行赋值操作
if(imgs[i].offsetTop<clientHeight+scrollTop && !imgs[i].getAttribute('src')){
//设置元素的属性:元素.setAttribute('属性名',属性值),如果之前没有这个属性,会新增;如果有,会将值修改
imgs[i].setAttribute('src',imgs[i].dataset.src)
n=i+1
}
}
}
方法2:getBoundingClientRect()
该方法的返回值包含整个元素的最小矩形(包括 padding 和 border-width)。该对象使用 left、top、right、bottom、x、y、width 和 height 这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
getboundingClientRect().top可以获取元素距离视口顶部的高度
这里我们去判断图片是否已经进入视口来决定是否给该图片的src赋值。
function lazyLoad() {
const imgs = document.querySelectorAll('img')
let n = 0
for (let i = n; i < imgs.length; i++) {
const imgTop = imgs[i].getBoundingClientRect().top
if (imgTop < window.innerHeight && !imgs[i].getAttribute('src')) {
imgs[i].setAttribute('src', imgs[i].dataset.src)
n = i + 1
}
}
}
方法调用
由于图片是被用户采用滚动的形式来查看,因此我们可以监听window的scroll,只要页面发生滚动就要调用我们的lazyLoad方法
window.addEventListener('scroll', lazyLoad)
这样的话我们会发现在页面初始时,位于可视区域的图片没有显示,因为初始化时页面没有滚动,我们需要手动调用一次该方法
lazyLoad()
效果如下:
再次性能优化
尽管我们已经实现了要求,但是如果是事件监听的话,意味着每滚动一下都会调用lazyLoad函数,会造成不必要的资源浪费,所以使用节流函数来减少实践调用次数
首先得知道什么是节流:规定在一个单位时间内,只能触发一次函数。如果这个函数单位时间内触发多次函数,只有一次生效。
我使用了时间戳和定时器共同实现节流,代码如下:
function debounse(fn,delay){
let start=0
let timer
return function (){
if(!start) start=new Date()
let now=new Date()
//上一次到现在还剩多少时间
let time=delay-(now-start)
//处理this的指向和传递参数
let context=this
let args=arguments
//再次被调用时清空上一个定时器
clearTimeout(timer)
if(time<0){
fn.apply(context,args)
start=now
}else{
timer=setTimeout(()=>{
fn.apply(context,args)
start=now
},time)
}
}
}
最后调用的时候就直接调用debounce函数就可以了
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图片懒加载</title>
<style>
img {
width: 510px;
height: 600px;
margin-top: 100px;
display: block;
}
</style>
</head>
<body>
<img data-src="./img/h1.png" alt="">
<img data-src="./img/h2.png" alt="">
<img data-src="./img/h3.png" alt="">
<img data-src="./img/h4.png" alt="">
<img data-src="./img/h5.png" alt="">
<img data-src="./img/h6.png" alt="">
<script>
// 方法1
// function lazyLoad() {
// const imgs = document.querySelectorAll('img')
// const clientHeight = document.documentElement.clientHeight
// const scrollTop = document.documentElement.scrollTop
// //n是第0张图片,为了减少for循环次数,对于前面的图片将不再进行判断操作
// let n=0
// for (let i = n; i < imgs.length; i++) {
// //不仅仅是高度的判断,同时自身属性如果已经有src属性,就不需要在进行赋值操作
// if(imgs[i].offsetTop<clientHeight+scrollTop && !imgs[i].getAttribute('src')){
// //设置元素的属性:元素.setAttribute('属性名',属性值),如果之前没有这个属性,会新增;如果有,会将值修改
// imgs[i].setAttribute('src',imgs[i].dataset.src)
// n=i+1
// }
// }
// }
// 方法2
function lazyLoad() {
const imgs = document.querySelectorAll('img')
let n = 0
for (let i = n; i < imgs.length; i++) {
const imgTop = imgs[i].getBoundingClientRect().top
if (imgTop < window.innerHeight && !imgs[i].getAttribute('src')) {
imgs[i].setAttribute('src', imgs[i].dataset.src)
n = i + 1
}
}
// console.log('被调用了')
}
function debounse(fn,delay){
let start=0
let timer
return function (){
if(!start) start=new Date()
let now=new Date()
//上一次到现在还剩多少时间
let time=delay-(now-start)
//处理this的指向和传递参数
let context=this
let args=arguments
//再次被调用时清空上一个定时器
clearTimeout(timer)
if(time<0){
fn.apply(context,args)
start=now
}else{
timer=setTimeout(()=>{
fn.apply(context,args)
start=now
},time)
}
}
}
lazyLoad()
window.addEventListener('scroll', debounse(lazyLoad,1000))
</script>
</body>
</html>