面试题:图片懒加载的实现原理

46 阅读4分钟

图片懒加载的实现原理?

什么是图片懒加载:懒加载简单理解就是懒惰的加载,在首屏渲染时,如果我们看不到图片,那就先不加载图片,这种合理的偷懒就是懒加载,大大减少了首屏的渲染时间,核心思想就是控制视口外的图片在进入视口时候在进行加载。

图片懒加载的关键就是:判断一个元素是否在可视区域

图片通常采用<img>标签 background 两种形式来加载:

img 标签:

首先 img 标签通过 src 属性的 url 来加载图片,因此将需要懒加载的图片 url 添加到 data-src 属性,这样 src 为空就不会加载图片了,然后需要判断条件来触发图片加载,实现方式如下:

方案一:img 元素.offsetTop<=window.innerHeight+window.scrollY

offsetTop 返回的是一个相对于最近的已定位的祖先元素顶部的距离,以像素为单位.如果元素没有已经定位的祖先元素,那么它将返回相对于文档顶部的距离

// 获取全部img标签
var images = document.getElementsByTagName('img')
// 添加滚动事件
window.addEventListener('scroll',(e)=>{
  // 触发回调
  ergodic();
})
function ergodic(){
  // 遍历每一张图
  for(let i of images){
      // 判断当前是否在可视区范围内
    if(i.offsetTop<=window.innerHeight+window.scrollY){
      // 获取自定义的值并且赋值给src属性
      let trueSrc = i.getAttribute('data-src);
       i.setAttribute('src',trueSrc)
    }
  }
}

方式二:getBoundingClientRect()

getBoundingClientRect().top 为元素相对于窗口顶部的位置,window.innerHeight 为当前窗口的高度;当元素对于窗口的位置小于当前窗口的高度时,自然就处于窗口的可视区了

// 获取全部img标签
var images = document.getElementsByTagName("img")
// 添加滚动事件
window.addEventListener("scroll", (e) => {
  // 触发回调
  ergodic()
})
function ergodic() {
  // 遍历每一张图
  for (let i of images) {
    // 判断当前是否在可视区范围内
    if (i.getBoundingCilentRect().top <= window.innerHeight) {
      // 获取自定义的值并且赋值给src属性
      let trueSrc = i.getAttribute("data-src")
      i.setAttribute("src", trueSrc)
    }
  }
}

offsetTop 和 getBoundingClientRect().top 的区别和相同点:

都是用于获取元素相对于视口或父元素位置信息的属性,区别就在于 gbcr 返回的是一个相对于视口顶部的距离,它是相对于视口的坐标值,如果元素在视口顶部之上,那么这个值就位负数;offsetTop 返回的是一个相对于最近的已定位的祖先元素顶部的距离,如果元素没有已定位的祖先元素,那么它将返回相对于文档顶部的距离;需要注意的是, gbcrtop 是相对于视口的动态值,它会随着滚动操作和窗口调整而变化,而 offsetTop 是一个静态值,它在元素加载时就确定了,并且不会受到滚动窗口调整的影响.

方式三:Intersection Observer 观察器接口

上面两种方式已经大致实现了懒加载,但是都有一个缺点,滚动事件会频繁触发,会触发大量的循环和判断操作,这里就引入一个叫 intersection Observe 的观察器,它是浏览器原生提供的构造函数,它能避免大量的循环和判断,缺点就是兼容性不太好

intersection Observe 这个构造函数的作用是它能够观察可视窗口与目标元素产生的交叉区域.简单来说就是当用它观察我们的图片时,当图片出现或者消失在可视窗口,它都能知道并且会执行一个特殊的回调函数,我们就利用这个回调函数实现操作

  1. 既然是一个构造函数,那么就可以通过 new 关键字生个一个实例
const observe = new Intersection Observe(callback);

其中会有回调函数作为参数,当目标元素显示和消失都会触发这个函数

  1. 用生成的实例 observe 通过 observe 属性可以为每一张图片绑定一个观察器;
for (let i of images) {
  observe.observe(i)
}
  1. 当回调函数执行时,函数会携带一个参数 entries,这个参数是一个数组,数组里面的元素就是当前改变了状态,触发了事件的目标元素,其中有 isIntersecting 属性,当目标元素可视口出现时为 true,消失为 false,我们就可以利用这个属性去判断并且将图片的 src 属性值设置为 data-src 保存的值.
function callback(entries) {
  for (let i of entries) {
    if (i.isIntersecting) {
      let img = i.target
      let trueSrc = img.getAttribute("data-src")
      img.setAttribute("src", trueSrc)
    }
  }
}

其中 target 为触发事件的元素;当前,当回来滚动时,图片会一会可见一会不可见,它都会触发这个函数,所以当某图片已经加载过时我们需要将这个观察器停掉,利用 unobserve 属性可停掉

function callback(entries) {
  for (let i of entries) {
    if (i.isIntersecting) {
      let img = i.target
      let trueSrc = img.getAttribute("data-src")
      img.setAttribute("src", trueSrc)
      // 停止当前元素的观察器
      observe.unobserve(img)
    }
  }
}