前端知识之性能优化——实现图片懒加载

懒加载,意思就是,在当前网页,滑动页面到能看到图片的时候再加载图片

故问题拆分成两个:

  1. 如何判断图片出现在了当前视口 (即如何判断我们能够看到图片)
  2. 如何控制图片的加载

方案一: 位置计算 + 滚动事件 (Scroll) + DataSet API

如何判断图片出现在了当前视口

  • offsetTop: 返回一个当前元素到其顶部的内边距的距离,只读属性 offsetTop = 自身margin + margin(多个父元素)+ padding(多个父元素) + border(多个父元素)
  • scrollTop:当前元素顶部到浏览器顶部的距离,即元素滚动出去的距离,如果该元素无垂直方向的滚动条,那么该元素的scrollTop为0,该元素可能是个小数

offsetTop - scrollTop <= 视口高度

js 复制代码
function isInViewPortOfOne (element) {
  // 获取可视窗口的高度。兼容所有浏览器
  const screenHeight = window.innerHeight || document.documentElement.clientHeight
  	 || document.body.clientHeight;
  // 获取滚动条滚动的高度
  const scrollTop = document.documentElement.scrollTop;
  // 获取元素偏移的高度。就是距离可视窗口的偏移量。
  const offsetTop = element.offsetTop;
  // 加100是为了提前加载
  return offsetTop - scrollTop <= screenHeight + 100;
}

如何控制图片的加载

<img data-src="shanyue.jpg" />

首先设置一个临时 Data 属性 data-src,控制加载时使用 src 代替 data-src,可利用 DataSet API 实现

img.src = img.datset.src

方案二: getBoundingClientRect API + Scroll with Throttle + DataSet API

如何判断图片出现在了当前视口

引入一个新的 API, Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。

getBoundingClientRect()用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。

getBoundingClientRect()是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)。

js 复制代码
// clientHeight 代表当前视口的高度
img.getBoundingClientRect().top < document.documentElement.clientHeight;

方案三: ## IntersectionObserver API + DataSet API

方案二使用的方法是: window.scroll 监听 Element.getBoundingClientRect() 并使用 _.throttle 节流

一系列组合动作太复杂了,于是浏览器出了一个三合一事件: IntersectionObserver API,一个能够监听元素是否到了当前视口的事件,一步到位!

事件回调的参数是 IntersectionObserverEntry(opens in a new tab) 的集合,代表关于是否在可见视口的一系列值

其中,entry.isIntersecting 代表目标元素可见

js 复制代码
let images = document.getElementsByTagName('img') //HTMLCollection
let father = document.getElementsByClassName('box')[0] //拥有滚动属性的dom
let fatherHeight = father.clientHeight 
let imgArr = [...images]
const callback = (entries) => {
  // console.log(entries)
  entries.forEach(entry => {
    // isIntersecting表示是否在交叉区域内(可见区域)
    if (entry.isIntersecting) {
        const image = entry.target;
        const data_src = image.getAttribute('data-src');
        image.setAttribute('src', data_src);
        observer.unobserve(image)
        console.log('触发')
     }
  })
}

const observer = new IntersectionObserver(callback); //创建观察对象
imgArr.forEach(image => {
   observer.observe(image); //开始观察
})

总结

第二、三种方法是浏览器API,存在兼容性问题。比较推荐使用第一种位置计算的方法,不存在兼容问题。

方案demo

html 复制代码
  <div class="box">
    <div class="blue"></div>
    <div class="red"></div>
    <div class="blue"></div>
    <div class="content">
        <img data-src="./1.jpg" height="300px" alt="" id="img1">
    </div>
  </div>
js 复制代码
let images = document.getElementsByTagName('img') //HTMLCollection
let father = document.getElementsByClassName('box')[0] //拥有滚动属性的dom
let fatherHeight = father.clientHeight 
let imgArr = [...images]
function lazyLoad() {
    imgArr.forEach(item => {
        // 方案一
        if (item.offsetTop + item.clientTop - father.scrollTop <= fatherHeight) {
            item.src = item.getAttribute('data-src')
         };
         // 方案二
        if(img.getBoundingClientRect().top < document.documentElement.clientHeight) {
            item.src = item.getAttribute('data-src')
         }
     })
}
   father.addEventListener('scroll', _.throttle(lazyLoad, 50))
相关推荐
NiceCloud喜云5 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
wordbaby6 小时前
React Native + RNOH:跨页面数据回传的最佳实践与避坑指南
前端·react native
丷丩6 小时前
MapLibre GL JS第22课:查看本地GeoJSON
前端·javascript·map·mapbox·maplibre gl js
Front思7 小时前
AI前端工程师需要具备能力+
前端·人工智能·ai
ZC跨境爬虫9 小时前
跟着 MDN 学CSS day_29:(掌握文本与字体样式的核心艺术)
前端·css·ui·html·tensorflow
李子琪。10 小时前
网络空间安全深度实战:CSRF 漏洞原理剖析与基于 Token 的纵深防御体系构建(全栈实验报告)
前端·安全·csrf
冰暮流星10 小时前
javascript之history对象介绍
前端·笔记
IT_陈寒10 小时前
Vite热更新失灵?你可能漏了这个配置
前端·人工智能·后端
丷丩10 小时前
MapLibre GL JS第19课:实时更新要素
前端·javascript·gis·map·mapbox·maplibre gl js
Mr.Daozhi10 小时前
RAG 进阶实战:跑通 Demo 后我连续翻了 6 次车,逐一修复才真正可用(含 Gradio Web 版)
前端·数据库·langchain·大模型·gradio·rag·科研工具