图片渲染优化

以前谈过许多次图片问题。也给出了几种方案。在实际使用中这几种无疑是可行而且方便的:

  1. loading
  2. connection API +​​promise.all()​​异步加载图片
  3. 骨架屏
  4. 懒加载 + 占位图

但是在电商场景下,第一种方案是不可以的:我们不能为了一张图片而放弃整个内容对用户的正常展现。(尤其是这个图片还只是个背景图)

第二种方案在首页是有奇效的,但是笔者觉得限制太多 —— 如果对于“通用型”的方案来说,骨架屏似乎更适合中大型项目。
这个方案当时提出的场景是:在我校的实验系统首页会有一个超大的轮播图,但是它的作用只是展示和宣传。很显然,这个轮播图并不需要所有的图片都能展示出来,但是如果放任不管,在网络情况稍差的时候会有空白甚至闪白的问题出现。这是不好的!所以用​​​connection​​ API检测当前网络情况以决定展示轮播图还是一张图片。

最后一种方案应该是现在比较常用的。占位图还可以换做一个固定宽高的、有背景颜色的​​div​​​,减少一次图片的请求(以前去哪儿首页就这样做的)。
但是它的缺陷在于:如果图片太大,长时间的loading也会让用户觉得烦躁!

不做处理是不可能的。长时间的白屏或者闪白估计会让产品发疯。。。


雪碧图

CSS spirte是个好东西,它能大幅减少 http 开销。​​spirte + prefetch​​的组合可以让优化 http 请求的同时让其提早被加载:

<link rel="prefetch" href="xxx" />

prefetch是对浏览器的“暗示”:将来可能需要某些资源,但由浏览器决定是否加载以及什么时候加载这些资源。它的优先级比较低。

spirte的原理是将整个整个页面需要的图片都放到一张图中,通过​​transform​​移动到需要展示的地方。但还是那句话:如果有许多体积太大的图片,会让雪碧图的加载异常困难,从而带来不好的用户体验。

懒加载

刚进入网页页面就会有大批量的图片资源加载,这会间接影响页面的加载,增加白屏加载时间,影响用户体验。因此,我们的诉求就是不在可视化窗口内的图片不真正加载,尽可能减少本地带宽的浪费和请求资源的数量。

懒加载的优势很明显:

  • 减少带宽资源消耗,减少不必要的资源加载消耗。
  • 防止并发加载图片资源导致的资源加载阻塞,从而减少白屏时间。

实现简单的懒加载

实现的方式有两种:

  1. 通过​​scroll​​事件来监听视窗滚动区域实现。该方法兼容性好,绝大多数浏览器和WebView都兼容支持。
  2. 通过​​IntersectionObserver​​ API观察DOM是否出现在视窗内,该方法优点在于调用简单,只是部分移动端兼容没有上一种方式好。

两种形式都是在观察当前DOM是否出现在了可视窗口内,如果出现的话就将​​data-src​​中的图片地址赋值给src,然后开始加载当前的图片。

笔者之前专门研究过 ​​预加载和懒加载(点击查看文章)​​​,在其中封装了一个函数可供调用。但那是通过监听 scroll 的方式。
事实上,浏览器提供的​​​IntersectionObserver​​ API要更方便一些:

const root = 获取父元素;
const options = {
root: root,
// 这里是一个数组可以指定多个比例类似[0.25, 0.5, 0.75, 1]
threshold: [0],//交会处
rootMargin:"0px"//对视口进行收缩和扩张
}
const lazyIntersection = new IntersectionObserver(entires => {
// entires为监听的节点数组对象
entires.forEach((item,index) => {
console.log(item)
// console.log(item.target, item.isIntersecting? '可见': '不可见')
// isIntersecting是当前监听元素交叉区域是否在可视区域指定的阈值内返回的是一个布尔值
if(item.isIntersecting) {
console.log('可见')
item.target.src = item.target.getAttribute('data-src')
// 这里资源加载后就停止进行观察
lazyIntersection.unobserve(item.target)
}
// console.log(item)
})
}, options)

let data = Array.from(document.querySelectorAll('img'))
data.forEach(item => {
// observe用来观察指定的DOM节点
lazyIntersection.observe(item)
})

大图片渲染优化

你也许见过类似这张图:

杂谈:电商平台中的图片资源优化实战_css


普通图片加载方式和后来被提出的jpg渐进式加载png交错式加载相比简直不值一提。

事实上,渐进式加载在实际使用中更加常见 —— 笔者推荐jpeg格式的渐进式加载方式:你不必完整的下载完毕图片,就可以看到图片的内容了。

没错它是一种由模糊到清晰的加载方式。

看到好多文章用JS模仿这种方式加载图片。但是…你把图片产生的 http 消耗转换为单线程js阻塞。结果是用户看到页面/可交互时间变长了。这样好么?

用 photoshop 生成图片时有个“存储为web所用格式”,然后,其中那个连续勾选就是渐进式JPEG图片了:
切记!需要勾选那个转换为sRGB选项,在某些浏览器下,图像设置为CMYK会出现一些问题!
FireWorks等图像软件也是有类似的输出设置的。

渐进式图片的优缺点

  1. 渐进式图片一开始大小框架就定好,不会像基本式图片一样,由于尺寸未设定而造成回流 —— 提高的渲染性能;
  2. 渐进式图片也有不足,就是吃CPU吃内存。

长图渲染优化

在电商场景中,最难受的其实是商品详情页面,这里运营可能会配置一些商品的详细描述图文。不仅对图片的质量会比较高,同时图片也会非常长。那么很显然,我们并不可能说直接拿到图片就显示在页面上,如果用户的网速比较慢的情况下,页面上就会直接出现一个很长的白条,或者一张加载失败的错误图。这些很明显不是我们想要的结果。

怎么办呢?
笔者研究了我司的图片处理工具:当你点开看大图时,会发现只显示了图片的一部分,或者在大图区域第一次只展示最顶部一小部分的内容。

依照这个思路,我们可以做相应的切图优化,将一张长图分成多个等比例大小的多张图块,来进行一个分批渲染调优,减少单次渲染长图的压力。

这个步骤显然是后端处理的 ——拿Node来说,你可以下载 ​​gm​​​1​​ 或是 ​​ImageMagic​​​2

npm install --save gm

gm 中提供了用于剪裁的函数:

gm("img.png").crop(width, height, x, y)

ImageMagic 更加强大,不仅可以剪裁图片,还支持 图片格式转换 和 图片识别 等功能!

处理完成后可以存放在专门的目录下,然后监听 ​​scroll​​​ 或 ​​IntersectionObserver​​ API在用户下滑时不断请求“下一段”图片。

图片异常处理

图片的异常分为两种:加载异常渲染异常

加载异常

加载异常就是指“由于各种原因导致的图片请求失败”。因为图片属于资源,资源的异常会触发一个 ​​Event​​​ 接口的 ​​error​​​ 事件,这些 error 事件不会向上冒泡到 window,但能被捕获。而​​window.onerror​​​不能监测捕获。
但可以被​​​addEventListener​​捕获:

// 图片、script、css加载错误,都能被捕获
<script>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<img src="https://yun.tuia.cn/image/kkk.png">
<script src="https://yun.tuia.cn/foundnull.js"></script>
<link href="https://yun.tuia.cn/foundnull.css" rel="stylesheet"/>

// new Image错误,不能捕获
<script>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<script>
new Image().src = 'https://yun.tuia.cn/image/lll.png'
</script>

// fetch错误,不能捕获
<script>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<script>
fetch('https://tuia.cn/test')
</script>

渲染异常

渲染异常其实就是我们说的“页面上看不到图片、显示空白”。我们通常需要做的就是“降级处理” —— 比如给出一个失败的提示,或者给出降级的图片(比如用​​z-index​​加持的background、伪元素加持下的图片/SVG)。

切记!如果你用了loading做加载优化,不要让loading一直存在!


  1. gm官网:​​http://www.graphicsmagick.org/​​ ​​↩︎​
  2. ImageMagic官网:​​https://imagemagick.org/​​ ​​↩︎​