我在Medium浏览帖子的时候发现他们的图片加载效果真的很赞诶。首先载入一个低像素的模糊图片,然后逐渐转变为高清大图。这个过程体验真的很好,所以我希望能够明白他们是使用什么方法做到的。
Medium的技术
我使用WebPageTest测试这个页面的载入过程。如果你希望能够测试同样效果,可以打开Medium的页面,禁用cache减慢图片申请加载的过程,所以加载出原图的时间会稍久。这样就可以清楚看到整个图片的加载效果。
具体执行过程
使用div限定好图片展示的区域,Medium使用<div>标签并加入padding-bottom样式设定对应图片的展示尺寸。通过这样占位的方式可以防止在图片加载后出现整体页面回流的情况。这种方法通常被称为intrinsic placeholders
加载小尺寸(像素低)的图片,此时网页会先请求一个像素质量较低的小号缩略图(大小为原图的20%).这个小图片使用<img />标签,因此浏览器会在标签加载完成后,立即请求图片资源。
只要图片加载完成,它就会被“画”到<canvas />中。图片数据会被main-base.bundle.js文件中自定义的Blur()函数重新计算,可以看到它会产生模糊图片的效果。尽管有些不同,不过该函数与StackBlur的模糊函数实现效果是相似的。在模糊图片生成的同时,浏览器也会开始请求高清原图资源。
最后原图被加载到页面上,canvas会被隐藏,只展示原图。
最后的最后,感谢CSS的动画功能,上述所有转变过程会很流畅。
Markup
整个展示图片的结构
-
<figure>
-
<div>
-
<div/> <!-- 这个div用于做图片加载过程中的占位符 -->
-
<img/> <!-- 低像素的缩略图 -->
-
<canvas/> <!-- 给上面的缩略图加上模糊效果 -->
-
<img/> <!-- 展示的高清无码原图 -->
-
<noscript/> <!-- fallback for no JS -->
-
</div>
-
</figure>
-
<figure name="7012" id="7012" class="graf--figure graf--layoutFillWidth graf-after--h4">
-
<div class="aspectRatioPlaceholder is-locked">
-
<div class="aspect-ratio-fill" style="padding-bottom: 66.7%;"></div>
-
<div class="progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded" data-image-id="1*sg-uLNm73whmdOgKlrQdZA.jpeg" data-width="2000" data-height="1333" data-scroll="native">
-
<img src="https://cdn-images-1.medium.com/freeze/max/27/1*sg-uLNm73whmdOgKlrQdZA.jpeg?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail">
-
<canvas class="progressiveMedia-canvas js-progressiveMedia-canvas" width="75" height="47"></canvas>
-
<img class="progressiveMedia-image js-progressiveMedia-image __web-inspector-hide-shortcut__" data-src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg" src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg">
-
<noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1800/1*sg-uLNm73whmdOgKlrQdZA.jpeg"></noscript>
-
</div>
-
</div>
-
</figure>
PS:实际图片大小要根据设备尺寸来设定。
尝试重新实现同样效果
我在CodePen重新通过使用CSS替代canvas实现同样的加载效果。下面的图片展示了整个加载过程中,图片的转变效果。
这么做是否值?
很明显,现在有许多种方法来实现同样的效果。要知道在几年前如此高性能的方式实现动画和模糊效果还是不可能的。但事实上,大多数时候延迟瓶颈,并不是设备本身的原因,因此这些技巧值得我们探索。 控制加载图片过程有以下优点:
懒加载:使用JS来请求资源让我们可以灵活控制图片资源选择。小图可以请求同一缩略图,大图则可以根据浏览器视窗大小来选择加载尺寸不同的图片。
更好的占位符: 相比于纯色占位符,使用缩略图添加模糊效果后会有更好的视觉效果,同时图片大小也只有2k左右不会牺牲负载。
裁剪图片大小:Medium根据访问设备的不同,返回不同尺寸的图片,这样可以很好的优化页面的加载速度,同时避免移动设备浪费过多流量。
其他版本
在实现Medium原方法之前,我觉得我可以在我的网站使用其他方法来实现。
内联图片数据
我们可以在img中添加缩略图的URLs来直接请求资源。这样做虽然会增加HTML的内容,但是可以加快占位符的生成速度。浏览器加载好HTML标签就立即下载图片文件资源。加了模糊效果后图片的质量就无所谓了,我测试使用0.5k大小的图片与2k大小的图片得到相似的显示效果。
模糊效果
默认情况下,当浏览器将一个小图像放大,它应用光滑效果处理图像的模糊效果。图像的效果也可以关闭,像QR码。
[…]the browser would render it in a way that didn’t make it look blocky[…] from Google Developers.
它可以在Chrome、Safari和Firefox中有效,尽管光滑效果在Chrome中更有效,你可以在这里看效果。
下面我们看看如何做到光滑效果。图片只有27px宽,并且像素非常低,将它放大会产生很可怕的效果。事实却并没有。如果上述效果能满足你的要求,那你就不需要更复杂的效果替换了。
上述图片模糊效果也可以使用CSS Filter Effects实现,它还支持IE浏览器哦(IE一生黑)。我相信Medium在使用canvas方法之前一定也尝试过使用这个功能更强的方法。但是可能是出于一定原因他们放弃了这一方法。这一方法的优点是你可以设定模糊度,并且可以通过CSS达成其他目的。
也可以使用SVG的filter来达成同样目的,如The “Blur Up” Technique for Loading Background Images 和 Textured Gradients in Pure CSS两篇文章提到的。
其他办法提升占位符:Google 图片搜索
他们选择图片的一种主颜色,并用其作为占位块的背景色。这样做会给用户一种图片加载速度更快的体验。
更先进的方法:Facebook的200 byte技术
年初Facebook发表过一篇"The technology behind preview photos"的文章,这篇文章主要说明如何预览一个没有JPEG头的42 * 42px的图片。 使用场景有些不同,这“图片”被用于Facebook的手机端,它知道如何组成一个有效的JPEG图片。此处我们在Web端使用的话需要编写JavaScript代码,这样做同样会增加存储资源。当然我们可以通过在服务器端组成这个图片解决这一问题,但是这样仍需要一些JavaScript代码发送申请图片资源的请求。
无论如何,这个方法对于Web端来说有点大材小用,但我还是希望能够将其作为一个参考。Using WebP for generating this preview images同样可以节省内存,并且不需要使用如此创造性的解决方法。
LQIP: Low Quality Image Placeholders
与其等待最终的图像呈现,我们可以先提供一个高度压缩的图片,然后切换到大图。这就是LQIP方法的思路。这一方法与Medium相似,不过是使用相同尺寸,但压缩更高的图片。
总结
随着页面加载的图片越来越多,需要勤于思考页面的加载过程。因为这会影响加载效率和用户体验。 如果你生成几个缩略图大小的图片,你可以实验使用一个非常小的图片作为背景,等待最终图片被加载出来。