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

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

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

懒加载

懒加载原理就是监听屏幕滚动事件,然后判断图片是否出现在屏幕内,等图片要出现在屏幕内再加载。可以使用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替换为真实图片路径,从而实现懒加载的原理。
  • 预加载原理:在加载页面的同时,开一个新线程去加载图片,当图片加载完毕后,将图片资源交给主线程去展示,从而实现图片的预加载。
相关推荐
豐儀麟阁贵5 分钟前
8.5在方法中抛出异常
java·开发语言·前端·算法
程序员念姐8 分钟前
软件测试系统流程和常见面试题
测试工具·面试
zengyuhan50335 分钟前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
Bro_cat35 分钟前
Java基础
java·开发语言·面试
醉方休38 分钟前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running1 小时前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔1 小时前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户4445543654261 小时前
Android的自定义View
前端
WILLF1 小时前
HTML iframe 标签
前端·javascript
枫,为落叶1 小时前
Axios使用教程(一)
前端