图片懒加载与预加载的实现

图片懒加载与预加载是前端优化中比较常见的方法,也是前端面试中会被问到的问题。懒加载就是当你打开一个页面时,没有出现在屏幕中的图片先不加载,等滑到该图片位置时再加载,而预加载就是一次性把图片加载完。

如果不做懒加载和预加载,浏览器的回流重绘很快,而图片的加载是需要发送网络请求的,当图片一百甚至一千张时,一次性发很多请求就会导致网络的堵塞,影响用户体验,接下来就让我们来实现一下懒加载以及预加载的效果。

懒加载

懒加载原理就是监听屏幕滚动事件,然后判断图片是否出现在屏幕内,等图片要出现在屏幕内再加载。可以使用js中的getBoundingClientRect()方法,获取元素的集合属性,打印如下。

js 复制代码
<body>
  <img src="" data-src="https://t7.baidu.com/it/u=1732966997,2981886582&fm=193&f=GIF" alt="">
  <script>
    let img = document.querySelector('img')
    let rect = img.getBoundingClientRect()  // 获取元素几何属性
    console.log(rect);
  </script>
</body>

left:左边框到窗口左边的距离,right:右边框到窗口左边的距离,bottom:下边框到窗口上边的距离,top:上边框到窗口上边的距离。

然后判断图片是否在容器内,图片的top小于窗口的高度,图片的bottom大于0。

为了让图片先不展示,就不能把图片的url放在src属性上,于是我们可以在img标签中自己定义一个属性data-src,将图片地址放进去,然后当图片要展示时,令图片的src等于data-src属性,展示过后的图片再移除掉data-src属性。

js 复制代码
<img src="" data-src="https://t7.baidu.com/it/u=17329669972981886582&fm=193&f=GIF" alt="">
js 复制代码
<script>
    let height = window.innerHeight

    function lazyLoad() {
      const imgs = document.querySelectorAll('[data-src]')
      // console.log(imgs);
      for (let i = 0; i < imgs.length; i++) {
        let rect = imgs[i].getBoundingClientRect() // 获取元素的集合属性
        if (rect.bottom > 0 && rect.top < height) {
          imgs[i].src = imgs[i].getAttribute('data-src')

          imgs[i].onload = function () {   // 图片被浏览器加载完毕
            imgs[i].src = imgs[i].getAttribute('src')
          }

          imgs[i].removeAttribute('data-src')
        }
      }
    }

    lazyLoad()

    window.addEventListener('scroll', lazyLoad)
  </script>

当然里面的imgs[i].src = imgs[i].getAttribute('data-src')可以换成如下:

js 复制代码
// imgs[i].src = imgs[i].getAttribute('data-src')
// imgs[i].onload = function () {     // 图片被浏览器加载完毕
//     imgs[i].src = imgs[i].getAttribute('src')
//  }
 
// 换成

let newImg = new Image()    
newImg.src = imgs[i].getAttribute('data-src')   // 读取 url
newImg.onload = function () { // 图片被浏览器加载完毕
    imgs[i].src = newImg.getAttribute('src')     
}

目的是为了将图片的加载过程异步化,以此来减轻主线程的压力。这是通过创建一个新的Image对象,然后设置其src属性来预加载图片,一旦图片加载完成(onload事件触发),再将图片的src属性设置到原来的img元素上。

效果如下:

预加载

用户一打开页面,全部图片先加载,然后后用户滑动就会非常地丝滑,但是就会导致同一时间加载图片过多,对服务器压力过大。

原理:在加载页面的同时,创建第二个线程去加载图片,当图片加载完毕后,将图片资源交给第一个线程去展示,从而实现图片的预加载。

代码如下;

js 复制代码
  <div id="pic"></div>

  <script>
    let pic = document.getElementById('pic')

    let arr = [xxxxx]   // 图片资源
    // 创建一个新的线程
    const worker = new Worker('worker.js')
    // 将数据发送给子线程
    worker.postMessage(arr)
  </script>

worker函数是js自带的一个函数,作用就是创建一个新线程,但是这个新线程不能操作dom结构。这两个线程可以通信,通过postMessage进行通讯。

然后worker.js里面去加载图片,直接通过http请求去加载资源,回来的是blob类型的文件。

js 复制代码
// worker.js   self就是worker

self.onmessage = function (e) {
  // console.log(e.data);   // 传过来的图片资源
  // 将数组中的地址资源加载出来
  let arr = e.data;
  for (let i = 0; i < arr.length; i++) {
    let xhr = new XMLHttpRequest();
    xhr.open("get", arr[i], true);
    xhr.responseType = 'blob'  // 文件类型
    xhr.send();
    xhr.onload = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        // console.log(xhr.response);
        self.postMessage(xhr.response);   //将图片发送给主线程
      }
    }
  }
}

然后主线程去接收资源再进行渲染;

js 复制代码
<body>
  <div id="pic"></div>

  <script>
    let pic = document.getElementById('pic')

    let arr = [xxxxx]   // 图片资源
    // 创建一个新的线程
    const worker = new Worker('worker.js')
    // 将数据发送给子线程
    worker.postMessage(arr)
    // 接收子线程发送的数据
    worker.onmessage = function (e) {
      console.log(e.data);
      const img = new Image()
      // console.log(window.URL.createObjectURL(e.data));
      img.src = window.URL.createObjectURL(e.data)
      pic.appendChild(img)
    }
  </script>
</body>

window.URL.createObjectURL(e.data)是window自带的方法,作用就是将blob资源转换成url。

小结

  • 懒加载原理:当页面加载完毕后,判断在可视区域内的图片先加载,当用户滚动时,判断图片是否进入可视区域,如果进入可视区,则将图片的src替换为真实图片路径,从而实现懒加载的原理。
  • 预加载原理:在加载页面的同时,开一个新线程去加载图片,当图片加载完毕后,将图片资源交给主线程去展示,从而实现图片的预加载。
相关推荐
PAK向日葵11 分钟前
【算法导论】PDD 0817笔试题题解
算法·面试
加班是不可能的,除非双倍日工资2 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip3 小时前
vite和webpack打包结构控制
前端·javascript
excel4 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼4 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT4 小时前
promise & async await总结
前端
Jerry说前后端4 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化