Demo视频

文章结构

文章基本思路:“目的 => 方案 => 验证 => 总结启发

一,图片懒加载实现
二,预期效果
三,A/BTest验证
四,总结

一,图片懒加载实现

实现图片懒加载的目的是在不影响用户体验的前提下,尽可能节省用户流量甚至是电量。(可以参考Why lazy load images or video instead of just loading them?)

方案选择

图片懒加载的本质是将某一图片的加载推迟到用户看到或即将看到该图片的时候。实现图片懒加载目前有三种方案:

方案 具体描述 缺点
一. 小程序image组件的lazy-load属性 小程序 - image ①不支持配置(加载时机,预加载数量) ②仅在page和scroll-view中生效(意味着很难通用)
二,页面滚动监听 小程序页面监听页面滚动(onPageScroll),滚动时逐一比较图片节点的top值(可由wx.createSelectorQuery().select('yourselector’).boundingClientRect((ret)=>{}).exec()获得)与滚动位置的关系 监听滑动 & 遍历所有节点会对滚动性能产生一定的影响(如果节点从上到下遍历,能做一些优化,也能达到较好的效果
三,IntersectionObserver 采用IntersectionObserver API,交由浏览器内核底层判断图片是否与视窗相交,减少内核层与js层的跨进程通信,性能更好(小程序基础库1.9.3开始支持) 不兼容低版本浏览器内核

补充:
①除了方案一,方案二三都同时支持小程序和H5,这里仅以小程序为例。
②如有别的方案欢迎分享补充_

基于上面的方案分析和当前用户系统占用率分布(IntersectionObserver API支持率大于80%),我决定采用IntersectionObserver API来对当前项目做渐进式增强,后续随着用户设备更新,将能实现100%的优化覆盖。

方案实现

参考性能更优越的小程序图片懒加载方式,感谢分享!注意:原文章方案存在Android快速滚动到底部没有触发加载 的问题。

从前面的视频我们可以看到,优化的对象是商品详情图片列表,考虑到复用性,我把图片列表封装成lazyload-list组件,源码如下:

/*** lazyload-list* imgs: 图片链接数组,* lazyload: 是否开启lazyload功能,默认开启* <lazyload-list imgs="{{intros}}"></lazyload-list>*/
Component({properties: {imgs: {type: Array,value: [],},lazyload: {type: Boolean,value: true,}},data: {lazyImgs: [],classNote: 'lazy-img-'},ready: function() {// 接口兼容性判断,不支持新特性则回退到即时加载的方案if (!this.createIntersectionObserver) {// @TODO 可以改用基于滚动检测 & 高度判断的实现方案this.setData({ lazyload: false });} else {var lazyImgs = [];this.data.imgs.forEach(function(img) {lazyImgs.push({url: img,loaded: false})});this.setData({ lazyImgs: lazyImgs });this.lazyloadImg();}},methods: {// 说明:原方案用同一个observer监听第一张图片加载,然后再监听下一张图片加载,// 但在安卓系统下极快速滑动会出现第二张图片没有触发加载就跳到后面的图片的情况,导致后面的图片都加载不出来。// 因此为了稳妥起见,给每一张图片设置单独的observer。lazyloadImg: function () {var that = this;this.data.lazyImgs.forEach(function(img, i) {var intersectionObserver = that.createIntersectionObserver();var observeSelector = '.' + that.data.classNote + i; // css选择器// bottom:300 指距离底部以下300px即会触发事件intersectionObserver.relativeToViewport({bottom: 300}).observe(observeSelector, function(res) {intersectionObserver.disconnect();img.loaded = true;that.setData({['lazyImgs['+ i +']']: img})})});}}
})
<view><view wx:for="{{lazyImgs}}" wx:key="{{item}}" class="{{classNote + index}}"><image class="img" mode="widthFix" src="{{lazyload && !item.loaded ? '' : item.url}}"/></view>
</view>
.img {width: 100%;display: block;
}

实现过程

  1. 根据传入的图片链接数组渲染image标签:

    1. 设置src为空;
    2. 设置唯一的class(用于查找到唯一的标签,添加监听);
  2. 在DOM渲染完成后(ready事件),添加IntersectionObserver监听:
    1. 接口兼容性判断,向后兼容;
    2. 根据上面设置的唯一class找到DOM节点,给所有图片DOM节点添加IntersectionObserver监听;
  3. 用户滚动(或者一进到页面就看到了图片)触发添加IntersectionObserver回调,加载图片
    1. 设置当前图片链接,加载图片;
    2. 删除当前节点监听;

具体接口请参考:小程序中的IntersectionObserver接口

补充-体验相关:图片应设置默认宽高(小程序本身有设置),图片加载前后高度变化尽可能小一些,对页面结构影响相对小,给用户观感会好很多。

二,预期效果

图片懒加载主要节省了用户没看到的图片的加载流量,节省流量在逻辑上是可以预期的。

但不同业务,不同场景用户的浏览情况不一样,例如当用户想买这个商品时,一般会认真浏览所有的商品图片,这个时候是会加载完所有的图片的,当然用户也没有浪费流量。而当用户只是从列表页点进来看看简单的介绍时,用户往往不会看商品详情图片,或者说不会看完所有的,那这部分没有显示图片的流量就能被节省下来。

逻辑推导出来的效果没有问题,符合预期。下面我们来看看在线上产品中的效果如何:到底能为用户节省多少流量?

三,A/BTest验证

对比验证

核心在于控制变量法。

为了知道使用图片懒加载列表组件**“到底能为用户节省多少流量”,我们需要对比优化策略上线前后平均每个用户在商品详情页花费的流量多少**。

简化到方便统计:

  1. 平均每个用户在商品详情页花费的流量
  2. 平均每个用户在商品详情页浏览的图片数量(假设每张图片的大小一致,实际上是30k~150k/张)
  3. 平均每个用户在商品详情页加载的图片比例(抹平不同商品详情页图片数量不一样的差异)
  4. 结论推导:如果图片加载比例在应用了优化策略后下降很多,说明图片懒加载优化能节省用户大量流量,如果下降不多,证明该业务该场景下实际优化意义不大。

传统方法

先统计原来一周内“平均每个用户在商品详情页加载的图片比例”,再统计图片加载优化策略上线后接下来一周内加载比例,进行对比,共耗时两周。(取完整一周作为统计周期是为了排除时间因素带来的影响)

实际统计过程如下:

  1. 给所有image标签绑定bindload事件,图片加载完成后计数 + 1;
  2. 离开页面前(onUnload,onHide / 或别的触发页面跳走的事件),计算加载比例 = 图片加载数量 / 图片总数
  3. 上报该比例;
  4. 后台统计:平均图片加载比例 = 所有上报加载比例之和 / 上报次数

高效的A/BTest

A/BTest相信大家都理解,A/BTest的核心优势在于能在线上并行测试多个方案。例如上面为了获取前后两个图片加载比率,我们需要耗时两周,而如果采用A/BTest,随机将用户分成两半,一半执行原有的图片加载策略,一半执行优化后的图片加载策略,我们只需要耗费一周就能得到我们想要的结果。

两周 => 一周 是A/BTest给我们带来最直观的收益,毕竟时间先机对于互联网公司的重要性不用再多说,而A/BTest因其并行性带来测试环境更加相似的特点则能减少别的无关因素影响(例如这一周没有搞活动,下一周确搞了运营活动,统计数据可能会产生误差),A/BTest更多相关内容请参考数据分析领域的沉淀,我就不班门弄斧了。

** 真的需要A/BTest吗?**看到这里说不定有同学已经发现,上面这个优化对比,根本不需要测两个图片加载比例,因为优化前的图片加载比例就是100%(全加载),因此根本用不上A/BTest,光测优化后的图片加载比例就行。说得很对!这个优化比较简单,理论上也能直观推导出结果。但如果是要在这个图片方案上做调整,例如对比上面提到的方案二,我们就不能直观推导出结果了,此时A/BTest将能发挥重要作用!

统计过程和上面传统方法的类似,就不赘述了,区别仅在于上报的时候加上参数来区分两种图片加载方案,下面重点说说:

简单的A/BTest实现 - 随机选取用户开启图片加载优化:

  1. 页面加载时,Math.random() > 0.5将用户随机分成两半;
  2. 将该用户类型记录进cookie里,避免下次进来再次被分组;
  3. 一般用户开启lazyload;
  4. 统计上报;
/*** lazyload-list* imgs: 图片链接数组,* lazyload: 是否开启lazyload功能,默认开启* preloadCount: 预加载图片数量* <lazyload-list imgs="{{intros}}"></lazyload-list>*/
Component({properties: {imgs: {type: Array,value: [],},lazyload: {type: Boolean,value: true,},preloadCount: {type: Number,value: 1}},data: {lazyImgs: [],classNote: 'lazy-img-',loadedCount: 0 // 图片加载数量统计},ready: function() {// A/BTest: 随机选取用户var key = 'lazyloadUserType';var userType = wx.getStorageSync(key);if (!userType) {// 设置用户类型var lazyloadOn = Math.random() > 0.5;userType =  lazyloadOn ? 'lazy' : 'normal';wx.setStorageSync(key, userType);}// 根据用户类型启用不同加载策略if (userType === 'lazy') {// 接口兼容性判断,不支持新特性则回退到即时加载的方案if (!this.createIntersectionObserver) {// @TODO 可以改用基于滚动检测 & 高度判断的实现方案this.setData({ lazyload: false });} else {var lazyImgs = [];this.data.imgs.forEach(function(img) {lazyImgs.push({url: img,loaded: false})});this.setData({ lazyImgs: lazyImgs });this.lazyloadImg();}} else {this.setData({ lazyload: false });}},detached: function() {// 图片加载比例计算var loadRate = (this.data.loadedCount/this.data.imgs.length).toFixed(4);// console.log(loadRate);// 数据上报},methods: {// 说明:原方案用同一个observer监听第一张图片加载,然后再监听下一张图片加载,// 但在安卓系统下极快速滑动会出现第二张图片没有触发加载就跳到后面的图片的情况,导致后面的图片都加载不出来。// 因此为了稳妥起见,给每一张图片设置单独的observer。lazyloadImg: function () {var that = this;this.data.lazyImgs.forEach(function(img, i) {var intersectionObserver = that.createIntersectionObserver();var observeSelector = '.' + that.data.classNote + i; // css选择器// bottom:300 指距离底部以下300px即会触发事件intersectionObserver.relativeToViewport({bottom: 300}).observe(observeSelector, function(res) {intersectionObserver.disconnect();img.loaded = true;that.setData({['lazyImgs['+ i +']']: img})})});},onImgLoad: function (e) {// 图片加载数量统计this.data.loadedCount += 1;}}
})

四,总结启发

经过一周的统计分析,使用图片懒加载列表组件后,图片加载比例:

// 2018-11-09~11-11线上数据

100% => 58% ↓42%

以上仅是特定项目特定页面的统计数据,仅供大家参考。

总结

从结果数据可以看到图片懒加载是web流量优化的基础,尤其是在长图片列表有明显的差异。应该将列表图片懒加载优化看做雪碧图压缩这种基础优化,有助于提升web应用的整体表现。

优化虽不复杂,但“确定目标,方案分析选择,预期推导,A/BTest验证,决策”这个方法能稳妥地解决问题。

启发

也叫TodoList,或者社区说法,挖坑。
需求繁忙,期待学有余力的朋友们共同探索。

  1. 采用方案二覆盖低版本机型 - 开源社区以及有很多版本;
  2. 封装纯js逻辑,方便swiper等组件实现图片懒加载;
  3. 学习现成的A/BTest系统:技术赋能产品做决策;

简单A/BTest验证图片懒加载效果相关推荐

  1. JS实现图片懒加载效果

    文章目录 前言 一.图片实现懒加载步骤 二.案例练习(三国女将) 1. html 代码 2. css 代码 3. js 代码 执行结果 前言 懒加载时一种也页面效果,可以提高页面加载速率,并降低服务器 ...

  2. 微信小程序媒体组件-image图片懒加载效果

    image 基础库 1.0.0 开始支持,低版本需做兼容处理. 图片.支持 JPG.PNG.SVG.WEBP.GIF 等格式,2.3.0 起支持云文件ID. 属性 类型 默认值 必填 说明 最低版本 ...

  3. 使用v-lazy图片懒加载插件

    图片懒加载效果 当我们上网冲浪的时候网络不顺畅,导致图片加载得相对较慢,容易造成页面塌陷的效果,使用图片懒加载插件,当图片没有加载完成的时候显示的是一张我们默认的照片(占位图片),图片加载后会自动替换 ...

  4. 使用jQuery实现图片懒加载原理

    在网页中,常常需要用到图片,而图片需要消耗较大的流量.正常情况下,浏览器会解析整个HTML代码,然后从上到下依次加载的图片标签.如果页面很长,隐藏在页面下方的图片其实已经被浏览器加载了.如果用户不向下 ...

  5. vue-lazyload图片懒加载的简单使用

    一.vue lazyload插件: 插件地址:https://github.com/hilongjw/vue-lazyload demo:http://hilongjw.github.io/vue-l ...

  6. 一个简单的图片懒加载

    一个简单的图片懒加载 所谓懒加载,是为了提高网页的打开的速度,获得更好用户体验的一种手段.其选择的重要的部分先加载,次要的部分需要的时候再加载.比如一个电商网站,首屏通常有很多的数据,清晰度较高的 b ...

  7. 小程序swiper怎么让内容撑开高度_小程序瀑布流组件:支持翻页与图片懒加载

    电商小程序中,用到瀑布流的地方非常多,每次都写一个瀑布流,重复一次逻辑,作为程序员,肯定是非常不愿意的. 瀑布流的形式都是大同小异,不同的是瀑布流中每个模块的内容,随业务而变化. 所以,我们把瀑布流框 ...

  8. “懒”的妙用——浅析图片懒加载技术

    1.定义 图片懒加载是一种网页优化技术.图片作为一种网络资源,在被请求时也与普通静态资源一样,将占用网络资源,而一次性将整个页面的所有图片加载完,将大大增加页面的首屏加载时间.为了解决这种问题,通过前 ...

  9. 按需加载图片(图片懒加载)

    前言 按需要加载图片,这是一个非常实用的功能,不仅可以提高网站的性能,还可以为你节省流量.对于用虚拟主机的朋友来说,如果你的网站是一个图片网站或者图片比较多的网站,那么图片懒加载功能真不能少. 原理 ...

  10. 小程序之图片懒加载[完美方案,你不来看看?]

    效果图 既然来了,把妹子都给你. 定义 懒加载,前端人都知道的一种性能优化方式,简单的来说,只有当图片出现在浏览器的可视区域内时,才设置图片正真的路径,让图片显示出来.这就是图片懒加载. 实现原理 监 ...

最新文章

  1. C#ListView控件添加Checkbox复选框并获取选中的数目,检查checkbox是否勾选
  2. 关于BP神经网络的大牛的论述
  3. PHP中文件操作基础:文件路径基础
  4. java 某年某月中第几周 开始时间和结束时间_重磅!库里又要签下一超级大合同!4年2亿啊!退役时间也定了...
  5. NetBeans 时事通讯(刊号 # 74 - Sep 30, 2009)
  6. 【Java并发编程】之十一:线程间通信中notify通知的遗漏
  7. 在Windows Mobile上隐藏你的应用程序
  8. iphone 计算机乱码,苹果电脑Word变乱码怎么办
  9. no.8 python 和 Linux (笔记)
  10. mailgun php版本,php – Mailgun发送带附件的邮件
  11. 汉中至巴中至南充铁路(汉巴南线)顺利开通
  12. Git本地文件上传到远程仓库
  13. AGV调式之软件开发—API调用
  14. 基础题库:12 甲流疫情死亡率
  15. 面试篇1:嵌入式C语言面试常见问题
  16. C++实验题8 数组使用(bushi)
  17. 腾讯试水6点下班,行业加班文化哪有那么容易破除?
  18. 配置HTTPS证书后,浏览器出现不安全提示的解决方法
  19. 风剑分享 | 只有数据最懂公司的痛点,指导企业决策走向
  20. 麦当劳叔叔用 AI 面试服务员,合格就录用

热门文章

  1. 光流的基本概念和原理-Lucas–Kanade光流算法
  2. php 速卖通产品采集,AliExpress(速卖通)关键词搜索结果采集 - 八爪鱼采集器
  3. 「冰狐智能辅助」如何实现在线实时调试?
  4. 浅析乡镇房地产产业现状及其对乡镇经济发展的推动力
  5. 在linux中安装google拼音输入法
  6. 网线线序和插座插头配线规则和光纤接口分类
  7. 黑客四种常用来攻击云服务器的手段
  8. LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback
  9. Python:打印星号
  10. vue渲染大量数据优化_Vue列表页渲染优化详解