Warning:
This wiki has been archived and is now read-only.

Proposals/img lazyload

From HTML5 Chinese Interest Group Wiki
Jump to: navigation, search

問題敘述 & 使用情節

  1. 在一個商品網頁裡的商品说明,有几张重要的商品照片,后面几十张商品各角度照片。瀏覽器會同時加载所有图片,重要的几张的反而看不到了。
  2. 相册类页面,可能需要展示上百张图片,但需要用户当前屏幕内的尽早出现,因此在屏幕外的可以延迟加载,待到进入或即将进入页面时再读取图片,以优化用户体验。
  3. 服务器配置较差,带宽和IO不足,需要尽量减少单个PV的数据传输量,70%的用户对页面只关注第一屏,因此第一屏以外的图片没有必要立刻加载,当用户滚动动页面时再行加载,以尽可能地节省服务器的开销。

當前作法

作法一

  1. 后端输出HTML时,所有需要LazyLoad的<img>标签的src属性设置为一张1x1的透明的gif图片,如transparent.gif,并将真实的图片地址设置在data-src属性上
    <img src="/images/transparent.gif" data-src="/images/real.png" />
  2. 监听window.onscroll事件
    window.onscroll = lazyLoadImages;
  3. 在事件中,找出所有需要LazyLoad且在可视区域中的<img>标签,并设置其src属性为预先存储的data-src属性。
function lazyLoadImages() {
    var images = document.getElementsByTagName('img');
    for (var i = images.length - 1; i >= 0; i--) {
        var image = images[i];
        var src = image.getAttribute('data-src');
        if (src && isVisible(image)) {
            image.src = src;
            image.removeAttribute('data-src');
        }
    }
}

function isVisible(element) {
    var style = window.getComputedStyle(element, null);
    if (style.getPropertyValue('display') === 'none') {
        return false;
    }
    if (style.getPropertyValue('visibility') === 'hidden') {
        return false;
    }

    return isInViewport(element);
}

問題:

  1. onscroll 不好計算,且用到boundingClientRect等会引起强制Layout,影响性能
  2. 无法依靠浏览器的更多资源来进行策略的选择,如:
    1. Reflow/Repaint不频繁时默认多加载图片
    2. 网络条件良好,带宽剩余较多时自动加载图片
    3. 根据用户的浏览习惯,多加载1-2屏的数据

提案

方案1. 增加onscrollintoview和onscrolloutofview事件

给Element添加2个事件:

  • onscrollintoview:当滚动条滚动后,元素进入到可视区域时触发
  • onscrolloutofview:当滚动条滚动后,元素离开可视区域时触发

在这2个事件的帮助下,可以通过监听onscrollintoview事件来实现迟加载:

<img src="/images/transparent.gif" data-src="/images/real.png" />
document.onscrollintoview = function(e) {
    if (e.target.nodeName === 'IMG') {
        var src = e.target.getAttribute('data-src');
        if (src) {
            e.target.src = src;
            e.target.removeAttribute('data-src');
        }
    }
}
注:注:例子中document.onscrollintoview,只是个演示.实际应用中.该事件,完全可以注册给我们关注的那个节点. 用document做演示.只是表明.我们可以借助委托.比如所有100张图片都有一个共同的最近祖先节点.通过对该祖先节点注册onscrollintoview事件.实现我们需要的功能.而不是对100张图片.分别注册onscrollintoview事件.


优点:

  1. 使用方便,省略了“是否在可视区域”的计算

缺点:

  1. 仅能控制通过滚动条滚动页面时的图片延迟加载

方案2. 增加DOMVisibilityChanged事件

给Element增加一个Mutation Event,名为DOMVisibilityChanged,在元素满足以下条件时触发且visibility为true:

  • 元素的BoundingClientRect与可视区域相交
  • 元素的display样式非none
  • 元素的visibility样式非hidden

当以上任一条件改变时,触发事件,且visibility为false

优点:

  1. Mutation Event对于频繁且大量元素触发的事件有更好的效果
  2. 浏览器同时加入对display和visibility样式的判断,更加精确

缺点:

  1. Mutation Event模型不成熟

方案3. 为所有加载外部资源的元素加入defer属性

给需要加载外部资源的元素,如img、iframe等加载一个defer属性

defer属性为Boolean Attribute,为true时表示在浏览器优化的特定情况下才开始加载,包括:

  • 在可视区域或可视区域外(诸如)1屏范围内
  • 元素可见(display/visibility)
  • 带宽有空余
  • 页面当前Layout/Paint不频繁,有足够空余来完成图片/iframe的渲染

优点:

  1. 浏览器可控性大,不需要javascript配合

缺点:

  1. 仅一个属性,可能造成浏览器策略不统一
  2. 浏览器的策略不一定是用户最好的浏览体验

方案4. 为所有加载外部资源的元素加入deferpolicy属性

deferpolicy属性是一个Enumrable Attribute,值有以下:

  • visibile:进入可视区域时加载
  • network-idle:当网络空闲时加载
  • render-idle:当渲染空闲时加载

以上规则可任意组合,无此属性则视为立刻加载

优点:

  1. 开发者可操控自由度大,由多个值可以组合使用

缺点:

  1. 实现复杂、理解难度较高

參見