Lightning Fast Image Previews with Pure CSS and LQIP

Matthias Endler

用纯 CSS 和 LQIP 实现闪电般快速的图片预览

原文由 Matthias Endler 发布,订阅该博客

改编自 Freepik
来源:改编自 Freepik

我的网站速度还算不错。

我希望无论你用什么设备、身在何处,每次加载页面都能感觉很流畅。这没什么好意外的,毕竟我只用了最基础的 HTML 和 CSS,能不用 JavaScript 就尽量不用。

还有一件事一直让我很困扰:图片加载完成后导致的布局重排。

问题在于,文本已经可以显示时,图片的尺寸还未知。结果就是,一旦上方的图片加载完成,文本就会被向下挤。

而且,在图片加载过程中没有任何预览,只有空白。以下是它在较慢网络下的样子:

无样式内容闪烁示意图
无样式内容闪烁示意图

我可以通过硬编码图片的宽和高来解决,但那样既繁琐又容易出错。而且还是没有预览效果。所以我在想,别人都是怎么做的。🤔

微型图片缩略图

我隐约记得Facebook 在移动应用中使用了微型预览缩略图。他们从 JPEG 文件头中提取量化表来渲染预览。这些信息会存储在客户端,所以不需要每次都重新下载。可惜,这种方法要求对图片编码器有完全的控制权。它适用于应用,却很难用于网站。

搜索仍在继续。

直到我的同事 Tobias Baldauf 向我介绍了 LQIP(低质量图片占位符)

它的思路是这样的:

  • 加载页面时直接内联低质量的图片缩略图。
  • 等页面完全加载后(例如触发 onload 事件时),再懒加载高质量原图。

遗憾的是,这种技术需要用到 JavaScript。不过我很喜欢这个想法,于是开始尝试不同的图片尺寸和格式。我的目标是用任意标准图片格式做出体积最小的缩略图。

基准测试

以下是用不同文件格式编码的 15 像素宽缩略图:

不同图片格式生成缩略时的对比
不同图片格式生成缩略时的对比

我用了不同的工具来生成缩略图。JPEG 和 PNG 编码用的是 svgexport

svgexport img.svg img.png "svg{background:white;}" 15: 1%

WebP 则用了 cwebp

cwebp img.png -o img.webp

GIF 是用在线工具转换的,并用 gifsicle 进行了优化:

gifsicle -O3 < img.gif > img_mini.gif

对比

WebP 体积最小,但并非所有浏览器都支持
GIF 体积排第二,但在缩放图片并应用模糊滤镜后,效果让我不太满意。
最终我选择了 PNG,它在体积和质量之间取得了很好的平衡。我还用支持 zopfli 压缩的 oxipng 对图片做了进一步优化。这样一来,缩略图的大小就只有大约 300 到 400 字节。

我把缩略图的生成流程集成到了博客的构建工具链中。实际生成图片的代码相当枯燥。如果你真的想看,代码在 GitHub 上

避免使用 JavaScript

以下是图片预览的 HTML 骨架:

<figure>
  <div class="loader">
    <object data="image.svg" type="image/svg+xml"></object>
    <img class="frozen" src="data:image/png;base64,..." />
  </div>
</figure>

技巧在于把完整尺寸的图片和预览图一起包进一个 loader 的 div 中,并给它加上 width: auto 的 CSS 属性:

.loader {
  position: relative;
  overflow: hidden;
  width: auto;
}

我用 object 标签来包裹 SVG,而不是用 img 元素。这样做的好处是,当 SVG 无法加载时我可以显示一个占位符。我把 object 定位在 loader 容器的左上角。

.loader object {
  position: absolute;
}
.loader img,
.loader object {
  display: block;
  top: 0;
  left: 0;
  width: 100%;
}

下面是占位符的 hack 写法,附带一些参考资料:

/* https://stackoverflow.com/a/29111371/270334 */
/* https://stackoverflow.com/a/32928240/270334 */
object {
  position: relative;
  float: left;
  display: block;

  &:after {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 1000px;
    height: 1000px;
    content: "";
    background: #efefef;
  }
}

最后一部分是对缩略图的处理。和大多数网站一样,我决定使用模糊滤镜。在某种程度上,这看起来就像图片被冻结住了一样,所以我把 CSS 选择器命名为 frozen。我还应用了缩放变换来实现清晰的边缘。

.frozen {
  -webkit-filter: blur(8px);
  -moz-filter: blur(8px);
  -o-filter: blur(8px);
  -ms-filter: blur(8px);
  filter: blur(8px);
  transform: scale(1.04);
  animation: 0.2s ease-in 0.4s 1 forwards fade;
  width: 100%;
}
@keyframes fade {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

我用 CSS 动画来代替 JavaScript。
动画时长基于所有访客页面加载时间的 95 分位数。虽然这只是一个近似值,但对大多数读者来说应该都适用。

成果

  • 无需 JavaScript
  • 兼容所有现代浏览器
  • 主图无法加载时有回退方案
  • 开销极小

参考资料

本文章由 muse-spark-1.2-contributor 进行翻译

评论