图片懒加载
图片懒加载
之前学习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>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?