用模糊优化图片加载

用模糊优化图片加载

这是一篇充满马赛克的文章。

弱网真是个头疼的问题。经常有客户反馈网页加载不出来啦、白屏啦、蓝屏啦......毫无头绪之时一查网络环境,好家伙,3G网络。

且不说这种网络是否来自某个地铁中或是某台电梯里,单想象一下眼前就能浮现出这样一种画面:漫长的等待过后首先加载出了文字,然后图片就像打印出来的一样,一条一条地憋出来——

等等,图片加载一定要这么难受么?

我们看看 Medium 的效果:

Medium的图片加载效果

图片加载相当迅速,但好像是蒙了一层纱,朦朦胧胧的。数秒过后,清晰的图片恍然浮在眼前。就像是一场梦,醒了很久还是很感动。

关于渐进式图片

最简便的方法自然是原生支持。其实常用的图片格式都支持一种渐进式(交错式)扫描格式。这种特殊的图片可以在下载时,首先加载出图片的大致轮廓,然后逐渐变清晰。在图片导出的时候,一般可以找到这么一种选项来快速提升图片加载的质量。

逐行扫描式
逐行扫描式
交错式图片
交错/渐进式

使用缩略图

当然,导出一张渐进式的图毕竟存在成本。要达到这种效果也可以另辟蹊径,比如Medium,大体上分为如下几步:

  1. 在原图片的位置贴一张同样大小的容器
  2. 在容器内加载一张缩略图,设置模糊效果(顺便说一下,之前的 Medium 会使用 canvas 来实现,目前已经换用了 css:filter 滤镜)
  3. 同时开始请求原图
  4. 原图下载完成后,隐藏掉缩略图容器,露出原来的图片(设置一个transition,让展现的过程更平滑)

其实,核心思想就是偷梁换柱——先用缩略图占个位,然后放心地去加载大图。通过 img 标签的 onload 事件,我们是可以知道图片什么时候加载完毕的:

<script>
    function handleImageLoaded () {
        // 图片加载完成
    }
</script>
<img src="origin url" onload="handleImageLoaded()">

假如没有 img 标签,比如是用 background 加载的图片,那么创造一个 Image 对象也能做到:

const imgDom = new Image()
imgDom.onload = () => {
    // 图片加载完成
}
imgDom.src = "origin url"

假设有这样一张原图:

首先是 img 标签,可以用 opacity 来控制缩略图的显隐,这样就可以通过 transition 使隐藏的过程平滑化。

<style>
    .img-container {
        position: relative;
        width: 400px;
        height: 200px;
    }
    .thumb-image {
        position: absolute;
        filter: blur(4px);
        transition: opacity 0.3s ease;
    }
</style>

<script>
    function handleImageLoaded() {
        // 图片加载完成
        const thumbImgDom = document.getElementById('thumb')
        thumbImgDom.style.opacity = 0
    }
</script>

<div class="img-container">
    <img class="thumb-image" id="thumb" src="img_thumb.jpg">
    <img class="origin-image" src="img_origin.jpg" onload="handleImageLoaded()">
</div>

其实,只靠一个 img 标签也能实现,只不过流程换成了:展示缩略图并设置模糊-同时下载原图-原图下载成功后替换 src-关闭模糊。同时,transition 的对象由 opacity 变成了 filter。那么用单标签的方法试一次 background 图片:

<style>
    .bg-image {
        width: 400px;
        height: 200px;
        background-image: url("img_thumb.jpg");
        background-size: 100% 100%;
        background-repeat: no-repeat;
        filter: blur(4px);
        transition: filter 0.5s ease;
    }
</style>

<script>
    const imgDom = new Image()
    imgDom.onload = () => {
        // 图片加载完成
        const imgDom = document.getElementById('bg-image')
        imgDom.style.filter = 'none'
        imgDom.style.webkitFilter = 'none'
        imgDom.style.backgroundImage = 'url(img_origin.jpg)'
    }
    imgDom.src = "img_origin.jpg"
</script>

<div id="bg-image" class="bg-image"></div>

这样,就通过两种思路实现了一个图片模糊化加载的流程。

在公司里恰好有个项目,用h5去做一个全屏动画。涉及的切图比较多,自然就想到弱网会是什么表现:

优化前

经过优化过后,虽然加载图片的总时间没怎么变,但是看起来好了很多:

优化后

参考资料