Bootstrap

2.14 & 2.15 image组件:如何实现图片的懒加载?(拆分为两节了)

视频链接:

2.14 image 组件:如何实现图片的懒加载?

片 1

 

上节课我们学习了页面导航组件,以及如何自定义实现导航栏。这节课我们学习 image 组件,这是一个最常使用的媒体组件之一,这个组件本身它就有一个 lazy-load 属性,这个属性已经实现了图片的懒加载功能,那么,我们为什么还要自定义实现呢,这节课我们就看一看这个问题。

 

首先我们先看看一些与 image 组件有关的技术问题。

 

什么是 webp?

片 2

 

webp 是 image 组件的一个布尔属性,开启这个属性之后,代表 url 可以设置 webp 格式的图片。

 

WebP【发音:weppy】是一种同时提供了有损压缩,与无损压缩的,并且是可逆压缩的图片文件格式。这种文件格式是由 Google 推出的。

 

image 组件默认是不解析 webP 格式的,它只支持网络图片资源,只有开启了 webp 这个属性后,才可以解析 webp 图片网址。

 

那么我们为什么要用 webp 呢,它有什么优势呢?

 

WebP 的优势体现在,它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,同时它还同时具备了无损和有损的压缩模式、Alpha 透明,以及动画的特性,对 JPEG 和 PNG 图片格式的转化效果都是相当优秀的。

 

有科技博客曾经有报道,YouTube 上的视频略缩图,在采用 WebP 格式以后,网页加载速度提升了 1/10;谷歌的 Chrome 网上应用商店,采用 WebP 这种格式以后,每天节省了几 TB 的带宽。

 

WebP 既可以替代 jpg、png 图片,还可以替代 gif 图片。国内有许多公司,包括大厂,现在都在使用这种格式了。

 

智图:

片 3

 

对于不是 webp 格式的图片,有什么简单的方法把它转成 webp 格式吗?

 

答案是有的,上面这个就是一个在线的转换网址。

 

对于转换后的 webp 图片,直接用谷歌浏览器就能打开,谷歌浏览器本身就支持打开这种 webp 格式。

 

如何理解 show-menu-by-longpress 这个属性?

片 4

 

show-menu-by-longpress,这是 image 组件的一个布尔属性,开启它,代表开启长按图片显示,识别小程序码的菜单。

 

 

片 5

 

但是这种识别,仅是在 wx.previewImage 中,预览图片的时候,才可以识别(见上面)。在真机设备上测试,直接在 image 图片上长按,是没有效果的,但在微信开发者工具中长按也是有效果的(见上面)。

 

官方的 image 组件已经有了一个 lazy-load 属性,为什么我们还要再另外自定义实现一个懒加载组件呢?

片 6

 

这也是我们这节课开始时就提出的问题。

 

官方 image 组件的 lazy 属性,它规定图片即将进入一定范围之内,这个范围是上、下三屏,在即将进入这个范围的时候,才开始加载图片。

 

那么,上下三屏,其实是一个很大的空间,一般情况下呢,这会导致我们的图片在页面加载时,第 1 次就可能加载几十张图片。

 

片 7

 

从上面这张截图可以看出(见上面),使用开启了 lazy-load 属性的 image 组件,第一页什么操作也不做,一上来,图片请求就发出了 31 次。

 

并且,image 组件提供的图片懒加载功能,限制很多,它只针对 page 与 scroll-view 下面的 image 组件有效。

 

为了验证我们在 Console 面板里看到的加载次数情况,我们可以使用微信开发者工具里面的 Audits 面板,这个体验评分面板,我们可以使用它运行体验评分。

 

片 8

 

在评分结果中(见上面),我们看到,性能评分稍低,只有 77 分,程序在短时间内请求了 34 次图片。这还是使用了 lazy-load 属性之后的测试结果。

 

因为官方组件 image,虽然有一个 layz-load 属性,但由于种种原因,我们仍然需要自定义实现一个。

 

(可以从本课源码中查看示例,原生组件示例和自定义组件示例,以及运行效果)

 

mina-lazy-image 它是怎么实现的?

片 9

 

刚才我们看到的自定义实现的 mina-lazy-image 的运行效果。那么,它是怎么实现的呢?

 

主要原理,是使用 wx.createIntersectionObserver() ,使用这个接口创建了一个 IntersectionObserver 实例,在这里 Intersection 的意思是交叉,IntersectionObserver 可以理解为交叉监测,用这个实例判断,图片是否出现在视图窗口之中,进入窗口后再进行加载。

 

relativeTo(string selector, Object margins)

relativeToViewport(Object margins)

observe(string targetSelector, callback)

disconnect()

片 10

 

我们现在看一下,这个 IntersectionObserver 对象,它主要用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。它有四个方法。

 

第一个方法 relativeTo,这个方法使用选择器,指定一个组件节点作为参照区域。这个选择器,可以是 id 选择器,也可以是类选择器。

第二个方法 relativeToViewport,指定页面的视窗显示区域,作为交叉判断的参照区域。

第三个方法 observe,用选择器指定目标节点,并开始监听交叉状态的变化情况,变化情况会在 callback 回调函数中返回去。

第四个方法 disconnect,代表监听完成,现在停止监听,回调函数不再触发。

 

left number

right number

top number

bottom number

片 11

 

其中第二个方法 relativeToViewport,它的参数是一个对象,这个对象描述视图窗口的边界,共有上面 4 个字段。其中 left 是区域的左边界,right 是区域的右边界,top 是区域的上边界,bottom 是区域的下边界。这 4 个边界在使用的时候没有必要全部指定,可以只指定一个。在我们查看的 mina-lazy-image 组件的源码里面,它是只定义了 bottom 下边界。

 

miniprogram/miniprogram_npm/mina-lazy-image/index.js:

module.exports =

/******/ (function(modules) { // webpackBootstrap

/******/ // The module cache

/******/ var installedModules = {};

/******/ ...

/***/ (function(module, exports, __webpack_require__) {

片 12

 

现在我们看一看 mina-lazy-image 组件的源码实现。index.js 这个文件是由 webpack 根据模块源码构建出来的,在文件的开始有这样一大段像乱码一样的内容,每行前面都有注释符。

 

这段内容基本上在所有通过 npm 安装的模拟目标代码中都是存在的,每个模块都有,这是 webpack 自己实现的一套 module 机制,包括__webpack_exports__ 和__webpack_require__。这种模块机制它把每个模块的真正源码,包裹在一个函数里,同时实现在模块间实现引用和导出。

 

对于这一大段代码,我们了解它的作用即可,它大概就是为了实现模块化。接下来我们继续看这个组件它的懒加载是怎么实现的。

 

miniprogram/miniprogram_npm/mina-lazy-image/index.js:

addObserver: function addObserver() {

...

var observer = this.createIntersectionObserver();

observer.relativeToViewport(this.properties.viewport).observe('.lazy-image-comp', () => {

this.setData({

showed: true

});

this.clean();

});

this.observer = observer;

return true;

}

viewport: {

type: Object,

value: {

bottom: 0

}

}

片 13

 

在这段代码里,首先通过 this.createIntersectionObserver()创建了一个 IntersectionObserver 实例,这是在组件内创建的方法,在组件外可以用 wx.createIntersectionObserver()创建,但在组件内,要用 this.createIntersectionObserver()这个方法创建。

 

接着,使用 relativeToViewport 方法,将参考区域绑定到 bottom:0 的视图窗口之上,也就是从页面底部算起,只要图片区域出现了就开始加载。再接着通过 observe 方法,开始监听.lazy-image-comp 这个组件,这是我们自定义组件的顶层样式名称。

 

(可以从本课源码中查看示例及运行效果)

 

结束语

 

下节课我们来看一下,image媒体组件在开发中经常会遇到哪些问题。

 

小程序官方组件 image 的 lazy-load 为什么有的时候不起作用?

片 14

 

文档上说,这个属性只针对 page 与 scroll-view 下的 image 有效。那这句话怎么理解呢?

 

每个页面最外层都是 page 啊,每个 image 不都在 page 里面吗?

 

在这里指的是直接位于 page 容器下面,或者位于 scroll-view 容器下面。文档上又说,小程序懒加载需要在图片进入一定范围之内,也就是上下三屏的时候,才开始加载。 这也就意味着,当你的图片不足三屏的时候,所有 image 节点都在范围之内的,这时候很有可能一次都加载完了。

 

为什么有时候图片链接可正常访问,但 image 组件加载不出来图片?

片 15

 

image 控件拉取图片的本质,是使用 wx.downloadFile 这个接口加载。很多时候是由于图片格式不规范,例如线上的 SSL 证书有问题,文件描述信息例如 content-type、length 等信息不标准,还有可能服务器发生了 302 跳转等原因,导致拉取不成功,表现的现象就是图片加载不出来。

 

有时候网络不好,加载超时了,也不会显示,并不是因为图片不可以访问。同样的图片我们使用浏览器加载,可能就是显示的。现在小程序的 image 组件,已经支持 webp 这种图片格式了,在之前不支持的时候,如果给它的 src 属性设置一个 webp 的图片网址,它也是不能显示的。

 

对于网络不好的这种情况,我们可以用 image 组件的 binderror 处理,监听到错误以后,重新赋值,一般就可以解决这种问题。

 

这里面有一个关于 302 的错误,我们展开看一下。

 

我们浏览网页的时候,浏览器在不断地在接收服务器端的应答,从而决定下一步做什么,这个应答就是状态码,在 http 协议里面,状态码是三位数字,这个状态码共分为五类,分别是为 1××,2××,3××,4××,5××,分别以它们开头的。

 

302 是服务器返回的一个 http 状态码。302 对应生活中的例子,可以类比手机的呼叫转移功能,我们给 A 打号码,但是移动把我们的呼叫转移到了 B 号码上面。

 

在服务器这边,接到前端的一个 http 请求的时候,前端想加载 A 页面,但是 A 页面因为网站改版,现在不存在了,取而代之的是 B 页面,这时候服务器可以返回 302 的状态码,并且同时返回 B 页面的地址。这就是 302 页面跳转。

 

涉及到页面跳转的 http 状态码,一共有两个,301 与 302,两者都是转向,不同之处在于,301 是网页永久转移到了新位置,302 是请求的网页临时转移到了新网址。

 

我们在微博上,经常看到短链接,其实就是利用了服务器端 302 跳转实现的。虽然短链接有跳转,但经过测试我们发现,目前小程序 image 是支持短链接的,这肯定是微信团队在组件内部已经做了处理了,因为我们单独拿 wx.downloadFile 接口去加载一个短链接图片的时候,是直接返回 403 错误的,403 代表资源不可用。

 

小程序的背景图片如何实现全屏,如何适配所有机型?

片 16

 

这是个好问题,因为机型不同,尺寸也不一样,用一张图片适配所有机型,是不太容易的。

 

原图

scaleToFill

aspectFit

aspectFill

片 17

 

image 组件有一个关于缩放的属性 mode,我们先看一下这个属性。这个属性经常使用的值有三个(见上面)。

 

片 18

 

第一个值 scaleToFill,代表不保持纵横比例缩放图片,使图片的宽高完全拉伸至填满整个 image 元素。

 

片 19

 

第二个值 aspectFit,代表保持纵横比例缩放图片,使图片的长边可以完全显示出来。也就是说,可以完整地将图片显示出来,而不会对图片有任何的裁剪。

 

片 20

 

第三个值 aspectFill,代表保持纵横比例缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在一个水平或垂直方向是完整的,在另一个方向将会发生裁剪。

 

但是由于 image 在加载图片时存在缺陷,实现背景图片适配所有机型这个事情,最好不要用mode属性去实现,最好还是由 wxss 样式来实现。

 

/* 背景图样式 */

.container {

position: fixed;

width: 100%;

height: 100%;

background-color:azure;

top: 0;

bottom: 0;

left: 0;

right: 0;

z-index: -1;

}

.container::after {

content: "";

background: url( ) no-repeat center center;

background-size: cover;

opacity: 0.5;

top: 0;

bottom: 0;

left: 0;

right: 0;

position: absolute;

}

片 21

 

我们看这段样式代码,通过 background 样式可以给页面设置一个背景,其中 background-size: cover,和 image 组件 mode 属性的 aspectFill 值的效果,是类似的。

 

那么,样式有了,还有一个问题,背景图我们做成多大合适呢?

 

抛开机型多、尺寸多的 Android 机型不说,现在在 iOS 上现在随着新版 iPhone 不断发布,iOS 尺寸也越来越多了,我们要用一个尽量覆盖所有屏幕比例的大小做背景图片。

 

一般情况下,我们设计的时候,新建一个 750×1334 的背景,分辨率设置为 72。用这样的尺寸做背景图片。

 

(可以从本课源码中查看示例及效果)

 

如何剪切图片?

片 22

 

例如有时候,在上传图片的时候,需要对头像图片做一些裁剪,这是它的应用场景。

 

片 23

 

上面这是有人实现的一个小程序组件(网址见上面),可以将图片通过拖拽的方式选择范围,任意裁剪。原理也很简单,四角的控制点是通过 view 渲染出来的。图片加载之后,绘制到 canvas 画布上,选定裁剪范围之后,通过 wx.canvasToTempFilePath 这个接口生成一个临时图片,这就是我们选择的结果。

 

(可以从本课视频中查看示例及运行效果)

 

好,这节课我们就讲到这里。这节课我们主要学习了 image 组件,了解了如何自定义实现一个懒加载的 image 组件。下节课我们学习 live-player 与 live-pusher 组件,了解一下在小程序中如何实现直接的功能。

 

2020 年 5 月 31 日