图片懒加载

图片懒加载,长列表最大的问题就是图片太多,如果一次性把图片全部请求了,那么页面渲染速度会很慢,如果用户点不到,还会造成很大的浪费,甚至会有性能瓶颈。

为什么要使用懒加载呢?为了加速页面的加载速度,减少不必要的请求,可以将未出现在可视区域的图片暂不加载,等到滚动到可视区域后再去加载。这样提升了性能和提高了用户体验。

 

实现原理:初始状态,所有图片都有一个默认的 src, 指向本地的一个 默认图片 default.png , 并且把img的真实的地址放在 data-src上。当滚动时,判断元素是否在可视区域,如果在可视区域,那么再把 data-src 上的值写入真正的 src 中。

实现方法

判断元素是否在可视区域的方法

 方法一

document.documentElement.clientHeight 获取屏幕可视窗口高度

      通过 element.offsetTop 获取元素相对于文档顶部的距离

      通过 document.documentElement.scrollTop 获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离

    然后判断 offsetTop - scrollTop < clientHeight ,代表在可视区。

 方法二

   通过getBoundingClientRect()方法来获取元素的大小以及位置

The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.

这个方法返回一个名为ClientRectDOMRect对象,包含了toprightbottonleftwidthheight这些值。

假设const bound = el.getBoundingClientRect();来表示图片到可视区域顶部距离;
并设 const clientHeight = window.innerHeight;来表示可视区域的高度。

随着滚动条的向下滚动,bound.top会越来越小,也就是图片到可视区域顶部的距离越来越小,当bound.top===clientHeight时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。

也就是说,在bound.top<=clientHeight时,图片是在可视区域内的。



function isInSight(el) {
    const bound = el.getBoundingClientRect();
    const clientHeight = window.innerHeight;
    //如果只考虑向下滚动加载
    //const clientWidth = window.innerWeight;
    return bound.top <= clientHeight + 100; // +100是为了提前加载。
}



 

特别简单的一个实例



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<style type="text/css">
*{margin:0;padding: 0;}
img {display: block;width: 100%;height: auto;}
</style>
</head>
<body>
<img src="img/default.png" data-src="img/img1.png">
<img src="img/default.png" data-src="img/img2.png">
<img src="img/default.png" data-src="img/img3.png">
<img src="img/default.png" data-src="img/img4.png">
<img src="img/default.png" data-src="img/img5.png">
<img src="img/default.png" data-src="img/img6.png">
<img src="img/default.png" data-src="img/img7.png">
<img src="img/default.png" data-src="img/img8.png">
<img src="img/default.png" data-src="img/img9.png">
<img src="img/default.png" data-src="img/img10.png">
<script type="text/javascript"> 
    var imgs  = document.querySelectorAll('img'),len = imgs.length; 
    var n = 0;//存储图片加载到的位置,避免每次都从第一张图片开始遍历  
    window.onscroll = function() { 
        var seeHeight = document.documentElement.clientHeight; 
        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 
        for (let i = n; i < len; i++) { 
            if(imgs[i].offsetTop < seeHeight + scrollTop) {
                 if (imgs[i].getAttribute('src') == 'img/default.png'){ 
                   imgs[i].src = imgs[i].getAttribute('data-src'); 
                }
                 n = i + 1; 
                console.log('n = ' + n); 
            }
        } 
    }; 
</script> 
</body>
</html>



函数节流

在类似于滚动条滚动等频繁的DOM操作时,总会提到“函数节流、函数去抖”。

所谓的函数节流,也就是让一个函数不要执行的太频繁,减少一些过快的调用来节流。

基本步骤:

  1. 获取第一次触发事件的时间戳
  2. 获取第二次触发事件的时间戳
  3. 时间差如果大于某个阈值就执行事件,然后重置第一个时间
function throttle(fn, mustRun = 500) {
  const timer = null;
  let previous = null;
  return function() {
    const now = new Date();
    const context = this;
    const args = arguments;
    if (!previous){
      previous = now;
    }
    const remaining = now - previous;
    if (mustRun && remaining >= mustRun) {
      fn.apply(context, args);
      previous = now;
    }
  }
}



这里的mustRun就是调用函数的时间间隔,无论多么频繁的调用fn,只有remaining>=mustRunfn才能被执行。

 

简单封装



const checkVisible = (ele) => {
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
  let clientHeight = document.documentElement.clientHeight
  return ele.offsetTop - scrollTop < clientHeight
}

let imgs = document.getElementsByTagName("img"), nums = imgs.length

const lazyLoad = () => {
  for (let i = 0; i < nums; i++) {
    if (checkVisible(imgs[i]) && imgs[i].getAttribute("src") === "default.png") {
      imgs[i].src = imgs[i].getAttribute("data-src")
    }
  }
}

function throttle(fn, mustRun = 500) {
  const timer = null;
  let previous = null;
  return function() {
    const now = new Date();
    const context = this;
    const args = arguments;
    if (!previous){
      previous = now;
    }
    const remaining = now - previous;
    if (mustRun && remaining >= mustRun) {
      fn.apply(context, args);
      previous = now;
    }
  }
}



从图片本身优化

一般问题出在哪,就得追本溯源。目前我们能够用到的有以下几种方法。

  1. 更换图片格式;
  2. 通过特别的方式在保持清晰度的条件下来压缩图片,目前常用的是在
  3. 该网站压缩图片,一般压缩率很高;
  4. 由 png,jpg,jpeg,gif 转换为更好的 webp。具体webp详看(

WebP 的优势在与它更好的图像数据压缩算法,能够将图片转换为更小的体积,具有无损和有损的压缩模式。如果是选择了有损压缩,也拥有肉眼无法识别差异的图像质量。虽然它在页面渲染的时候浏览器比jpg会花稍长的时间解析它的算法,但是权衡它所带来的体积的减少来看,WebP 还是最优秀的。目前Google、Facebook、腾讯、阿里、美团的等国内外互联网公司广泛应用了webp,超过70%的浏览器已经支持webp,Safari和Foxmail也在进行支持webp的测试。


面临困难:

  1. 目前并不是所有浏览器都支持WebP,因此需要解决浏览器适配问题。
  2. 对于已上线运营的网站,采用WebP需要替换大量图片,工作量太大。


针对 WebP 兼容性问题,可以从两个方面入手

1.从服务端考虑:
如果浏览器支持 WebP ,那么会在 request header accept里,发送image/webp, 服务器收到以后根据这个来去返回给客户端图片。请求头里有,则发送webp的,如果没有,就发送普通格式的图片。

不过这个对服务器要求比较高,并且现在图片大多放在 CDN 上,CDN去做这种策略可能会稍微麻烦点。

2.从前端考虑:
前端去检测浏览器是否支持 WebP, 支持就发送 WebP 的图片请求,不支持就发送 jpg、png等。下面是一行代码判断是否支持WebP。



var isSupportWebp = !!\[\].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0;



!!\[\].map主要是判断是否是IE9+,以免toDataURL方法会挂掉。如果你直接对数组原型扩展了map方法,则需要使用!!\[\].map以外的方法进行判断,例如!!window.addEventListener等。