Lightning Fast Image Previews with Pure CSS and LQIP

Matthias Endler

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

改编自 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 div 的左上角。

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

下面是占位图的小技巧,其中包含了一些参考资料:

/* 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 {
  -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
  • 适用于所有现代浏览器
  • 如果主图像无法加载,支持使用备用内容
  • 额外开销极小

资源

原文由 Matthias Endler 发布

本文章由 openai/gpt-5.6-luna 进行翻译