基于canvas的图像预加载

在图片多的页面中,为了提高用户体验,我们会对图片进行压缩,采用更小的图片格式让图片尽可能快的展现给用户。在浏览medium.freecodecamp.com中一些文章时,偶然发现在网速不好的时候,会先看到一张模糊的图片,然后再展示出清晰的图。于是打开控制台抓了一下资源,发现展现出的第一张模糊的图片大小只有30*30。通过预加载一张小的图片,可以快速的展示给用户一些东西,减少中间的空白时间。看一看源码,发现是基于cancas来实现的。下面来看看它是怎么做的吧

首先我们有两张图
一张是原始图像:
demo
另一张是 30*30 的小图:
demo20

图片展示的 html 结构如下,原始图像我们先通过 visibility:hidden 隐藏起来。

1
2
3
4
5
<div class="img-container is-canvasLoaded" img-src="./demo.png">
<img src="./demo_min.png" id="imgdemo" alt="" style="opacity:0;position: absolute;z-index: -1;">
<canvas id="canvas-container"></canvas>
<img src="./demo2.png" alt="" id="origin-img" style="opacity:0">
</div>

初始时设置 canvas 的大小略大于我们的 demo_min.png 图片,如 75 * 75,然后:

  • 获取 demo_min.png 大小 60 * 30
  • 根据 demo_min.png 的比例,设置一下 canvas 的比例 75 * 37.5
1
2
3
4
5
6
7
8
9
10
11
var imgDemoSize = {
width: imgDemo.offsetWidth,
height: imgDemo.offsetHeight
}
var scale = imgDemoSize.width / imgDemoSize.height > canvasSize.width / canvasSize.height ? canvasSize.width / imgDemoSize.width : canvasSize.height / imgDemoSize.height
var scaleSize = {
width: imgDemoSize.width * scale,
height: imgDemoSize.height * scale,
}
canvasDom.width = scaleSize.width
canvasDom.height = scaleSize.height

现在html结构如下:

1
2
3
4
5
<div class="img-container is-canvasLoaded" img-src="./demo.png">
<img src="./demo_min.png" id="imgdemo" alt="" style="opacity:0;position: absolute;z-index: -1;">
<canvas id="canvas-container" width="75" height="37></canvas>
<img src="./demo2.png" alt="" id="origin-img" style="opacity:0">
</div>

将图像画在 canvas 上:

1
2
3
var context = canvasDom.getContext('2d')
// drawImage可以是HTMLImageElement
context.drawImage(imgDemo, 0,0,60,30,0,0,75,37)

canvas 的 drawImage 方法的第一个参数,允许是 HTMLImageElement 。所以这里直接用 img 元素比较方便。图像画在 canvas 上之后,我们就可以通过 canvas 的 getImageData 方法来获取图像的像素,然后对图像进行高斯模糊处理。模糊处理这里直接使用了 stackblur.js 。想了解高斯模糊原理的可以参考高斯模糊的算法

1
2
var imgData = context.getImageData(0,0,75,37)
var blurImgData = StackBlur.imageDataRGBA(imgData, 0,0, 75, 37, 5);

模糊处理完之后,putImageData 放在 canvas 中

1
context.putImageData(blurImgData, 0, 0);

然后展示 canvas , 到此页面上会展示一张模糊的图。

blur-result

模糊图片出来了,原始图像什么时候展示呢?
使用 Img 的 load 事件,原始的图像加载完成后触发 load 事件,将 canvas 隐藏,展示原始图像。

1
2
3
4
imgOrigin.addEventListener('load', function () {
imgOrigin.style.opacity = 1
canvasDom.style.opacity = 0
})

在网速略慢时,我们就会看到页面上会先出现一张模糊的图,然后再呈现出清晰的图。此外可以用 css 的 transition 属性,增加模糊图和原始图 opacity 过度时间,让图片之间的切换看起来更为自然顺畅。