Minimal CSS-only blurry-image-placeholders(LQIPs) 这是一个天才般的想法,它把原本需要用 js 解码的工作,直接放到 css 表达式里,不仅仅是计算加快了,而且消除了很多中间成本。
我们项目有用到类似的需求。在我们项目中,图片名称(url)的一部分包含了 blurhash。 简单来说,我们使用文件名来存在 blurhash,然后将这个 blurhash 字符串解码成图片,但这是有代价的,需要用一个小 canvas 绘制:
- 首先用算法绘制出模糊图片然后将它绘制到 canvas 上(消耗 CPU);
- 然后将图片导出数据(消耗 CPU 和内存,这里做一次编码);
- 最后将数据转成 blob-url(消耗 IO);
- 最终设置 image-src(消耗 Network-IO,这里做一次解码)。
尽管我已经将大部分流程放到 webworker 中,使用 offscreencanvas 来生产最后需要的 blob-url,从而避免对主线程的消耗,但是总成本是不会减少的。
这篇文章的方案,直接将算法硬编码到 css 表达式中,直接输出图片。原本依赖 js 方案中,很多中间的 CPU 和 IO 成本都消解了。 本质是把 background-image 直接当作一个 canvas 来进行绘制。所以我说它是天才般的想法!!
确实,如果直接在 image 上放一个绘制 blurhash 的 canvas,那么也就没那么多 CPU 和 IO 成本了。 然而如果真的放 canvas,内存成本会非常多,而且不同浏览器对于 canvas 的数量还会有限制。
于此同时,css-only 方案还有一个巨大的改进,就是图片的精度。 我们知道 background-image 到 gradient 绘制,底层走的是 GPU 绘制,它是极快的。因此不论图片尺寸多大,它始终是可以非常高分辨率的(当然你也可以用 backgrond-size 来控制最终的分辨率)。 虽然是模糊图片,但是高分辨率的模糊和低分辨率的模糊是两码事情。 低分辨率的图片,放大的时候,浏览器走的是 图像插值算法 (Image Interpolation)(通常使用双线性插值(Bilinear Interpolation) 或 双三次插值(Bicubic Interpolation),有一个 css 属性可以控制这种差值算法的使用:image-rendering: auto|smooth|crisp-edges|pixelated;
)。 然而这种插值算法的效果并不是那么理想,特别是我们用 blurhash 存储的其实就是几个图片像素而已,所以直接对一个低分辨率的图片进行插值,会出现很明显的“✨”效果。这里给一个例子:
然而在 js 方案中,我们如果要避免插值算法对模糊图的影响,只能是输出更高质量的图片,比如从 3*2
提升到 6*4
或者 12*8
,然而代价输出的 DataURL 变长,也就意味着 CPU 和 IO 的消耗增多。 因此在 css-only 方案中,这个问题可以从根源上避免,不用再受插值算法的困扰,使用 gradient 进行高精度的矢量绘制,不用担心模糊图的质量问题。