使用纯 CSS 和 LQIP 实现闪电般快速的图像预览
我的网站速度还算不错。
我希望每次页面加载都能让人感觉很快,无论你使用什么设备、身处何地。这并不奇怪。毕竟,我只使用普通的 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.webpGIF 使用在线工具转换,然后使用 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
- 适用于所有现代浏览器
- 如果主图像无法加载,支持使用备用内容
- 额外开销极小
资源
随机一篇博客