Warning:
This wiki has been archived and is now read-only.
Proposals/img lazyload
From HTML5 Chinese Interest Group Wiki
Contents
問題敘述 & 使用情節
- 在一個商品網頁裡的商品说明,有几张重要的商品照片,后面几十张商品各角度照片。瀏覽器會同時加载所有图片,重要的几张的反而看不到了。
- 相册类页面,可能需要展示上百张图片,但需要用户当前屏幕内的尽早出现,因此在屏幕外的可以延迟加载,待到进入或即将进入页面时再读取图片,以优化用户体验。
- 服务器配置较差,带宽和IO不足,需要尽量减少单个PV的数据传输量,70%的用户对页面只关注第一屏,因此第一屏以外的图片没有必要立刻加载,当用户滚动动页面时再行加载,以尽可能地节省服务器的开销。
當前作法
作法一
- 后端输出HTML时,所有需要LazyLoad的
<img>
标签的src属性设置为一张1x1的透明的gif图片,如transparent.gif,并将真实的图片地址设置在data-src属性上<img src="/images/transparent.gif" data-src="/images/real.png" />
- 监听
window.onscroll
事件window.onscroll = lazyLoadImages;
- 在事件中,找出所有需要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); }
問題:
- onscroll 不好計算,且用到boundingClientRect等会引起强制Layout,影响性能
- 无法依靠浏览器的更多资源来进行策略的选择,如:
- Reflow/Repaint不频繁时默认多加载图片
- 网络条件良好,带宽剩余较多时自动加载图片
- 根据用户的浏览习惯,多加载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事件.
优点:
- 使用方便,省略了“是否在可视区域”的计算
缺点:
- 仅能控制通过滚动条滚动页面时的图片延迟加载
方案2. 增加DOMVisibilityChanged事件
给Element增加一个Mutation Event,名为DOMVisibilityChanged,在元素满足以下条件时触发且visibility为true:
- 元素的BoundingClientRect与可视区域相交
- 元素的display样式非none
- 元素的visibility样式非hidden
当以上任一条件改变时,触发事件,且visibility为false
优点:
- Mutation Event对于频繁且大量元素触发的事件有更好的效果
- 浏览器同时加入对display和visibility样式的判断,更加精确
缺点:
- Mutation Event模型不成熟
方案3. 为所有加载外部资源的元素加入defer属性
给需要加载外部资源的元素,如img、iframe等加载一个defer属性
defer属性为Boolean Attribute,为true时表示在浏览器优化的特定情况下才开始加载,包括:
- 在可视区域或可视区域外(诸如)1屏范围内
- 元素可见(display/visibility)
- 带宽有空余
- 页面当前Layout/Paint不频繁,有足够空余来完成图片/iframe的渲染
优点:
- 浏览器可控性大,不需要javascript配合
缺点:
- 仅一个属性,可能造成浏览器策略不统一
- 浏览器的策略不一定是用户最好的浏览体验
方案4. 为所有加载外部资源的元素加入deferpolicy属性
deferpolicy属性是一个Enumrable Attribute,值有以下:
- visibile:进入可视区域时加载
- network-idle:当网络空闲时加载
- render-idle:当渲染空闲时加载
以上规则可任意组合,无此属性则视为立刻加载
优点:
- 开发者可操控自由度大,由多个值可以组合使用
缺点:
- 实现复杂、理解难度较高