图片渐进式加载方法探究

在现代 Web 开发中,开发者通过加载动画、骨架屏或模糊/低清占位图等策略,试图让应用在视觉上比实际加载速度更快。

传统派 🚬

假设有如下HTML

<!DOCTYPE html><html lang="en">    <head>        <meta charset="UTF-8">        <meta name="viewport" content="width=device-width, initial-scale=1.0">        <title>Document</title>    </head>    <body>        <!-- 没有任何占位处理的传统加载方式 -->        <img src="https://assets.vluv.space/4k-wallpaper.webp" alt="404"/>    </body></html>

这种写法虽然简单,但对于处于 2G 或弱网环境的用户来说,这往往意味着糟糕的第一印象。通过下方的甘特图,可以直观地注意到存在8秒的空窗期,期间用户无法观看到图片

         
      

即使现在通5G了,也可能有:

  • 博客服务器带宽🟰小水管
  • CDN限速/源站限速 etc.
  • 站内都是近百兆的高清好图 👍
  • 不排除个人喜好,就爱瞎折腾

总之,优化还是有用的。

LQIP 🌄

LQIP(Low-Quality Image Placeholder)技术由 Facebook 团队在 2015 年推广[1],最初是为了解决移动端封面图加载慢的问题。

该方案的核心思路并不复杂:

  • 服务端:在保持原始宽高比的前提下,对图片进行大幅压缩,生成一张体积极小的低分辨率图片;
  • 客户端:先将该低分辨率图片拉伸至目标尺寸,并叠加高斯模糊(Gaussian Blur)滤镜进行展示;

在FaceBook团队的技术文章中,为了极致的压缩,其还在客户端中预存标准的JPEG头,服务器仅发送版本号、宽高及约 200 字节的图像数据。

从时间轴上看,LQIP 的价值主要体现在 “原图加载完成之前” 的这段空窗期:用户并非看到纯色占位图,而是一个与最终内容高度一致的模糊预览。这种方式可以为用户提供一个较为丝滑视觉反馈,目前博客的封面图就是使用了该方案

         
      

下面的两个示例[2]可以直观感受到 LQIP 带来的体验提升

ThumbHash & BlurHash 🫨

在 LQIP 之外,业界还提出了 ThumbHashBlurHash 两种方案,它们在UX层面试图解决的是同一个问题:在原图尚未加载完成前,尽可能早地呈现一个与最终内容“形似”的视觉占位。不过,这两者的实现路径与 LQIP 不同:

技术原理

BlurHash 会提取一张图片,并生成一段仅 20–30 个字符的短字符串来代表这张图片的占位符。你在服务端的后台完成这一操作,并将该字符串与图片一起存储。当你向客户端发送数据时,会同时发送图片的 URL 和 BlurHash 字符串。客户端收到后,把字符串解码成一张模糊图片,在网络加载真实图片期间进行展示。该字符串足够短,可以轻松嵌入你使用的任何数据格式,例如直接作为 JSON 对象中的一个字段。[3]

ThumbHash[4],可以视为 BlurHash 的一次演进:

  • Encodes more detail in the same space
  • Also encodes the aspect ratio
  • Gives more accurate colors
  • Supports images with alpha

Limitation & Workaround

尽管 ThumbHash 与 BlurHash 在概念上非常优雅,但在 Web 场景中仍然存在一些绕不开的现实约束,即对 JavaScript 的强依赖

浏览器无法直接解析哈希字符串并渲染图像,必须依赖 JavaScript 解码库。而 JS 通常是在 HTML 解析完成后才开始执行,这意味着占位图的展示本身就可能出现可感知的延迟

一种折中的做法是:在服务端将哈希还原为 Base 64 图片,并直接嵌入到HTML

         
      

Summary

个人博客的封面图采用了 LQIP 方案,而文章内图片则使用了上面提到的折中做法。全站共计 400 余张图片,PNG 格式的 data URL 总体积约 1 MB,平均每张 2.5 KB。相比thumbhash/blurhash 20–30 Byte 的字符串,体积膨胀了 100 倍以上(但是减少了JavaScript依赖 😎)

If you transform the image to a blurhash and back to an image, then send the image to the customer, what you’ve done is treat BlurHash as a preprocessing filter that loses the majority of its benefits.

Justin Greer

然而在 Blur Up Thumbs 页面直观看了体积对比,才意识到自己其实走了点弯路。不过项目已经形成了路径依赖,也懒得推倒重来,于是退而求其次,把这些 data URL 转换成 WebP。最终,400 张图片的总体积降到了 约 110 KB,平均 275 Byte/张。

不算最优解,但是:🧄🐦🧄🐦(发出武汉声音)

References

Cover Image HTML

<style>.cover-image {  position: relative;  display: block;  overflow: hidden;  height: 380px;  img {    display: block; width: 100%; height: 100%; object-fit: cover;  }  .cover-lqip {    position: absolute; top: 0; left: 0; z-index: 1; filter: blur(10px);  }  .cover-origin {    position: relative; z-index: 2;  }}</style><a href="xxx" class="cover-image">  <img class="cover-lqip" src={lqip_src} alt="placeholder" />  <img class="cover-origin" src={cover} srcset="xxx" alt="xxx" decoding="async" loading="lazy"/></a>
Article Image HTML

<div class="pic"    style="aspect-ratio: 3638 / 2032; background-image: url(data:image/webp;base64,U...==); background-repeat: no-repeat; background-size: cover; width: 100%; max-width: 3638px; position: relative; overflow: hidden;">    <img onload="this.style.opacity=1; setTimeout(() => { this.parentElement.style.backgroundImage='none'; }, 600);"        src="https://assets.vluv.space/xxx.webp"        srcset="xxx"        style="object-fit: cover; opacity: 1; width: 100%; height: 100%; transition: opacity 0.6s ease-in-out;"        alt="xxx" decoding="async" loading="lazy"></div>

  1. 参考链接 The technology behind preview photos - Engineering at Meta ↩︎

  2. 视频Source: A clear look at blurry image placeholders on the web | Mux by Wesley Luyten ↩︎

  3. 原理摘自 BlurHash ↩︎

  4. ThumbHash: A very compact representation of an image placeholder ↩︎

图片渐进式加载方法探究

https://vluv.space/image_loading/

Author

GnixAij

Posted

2026-01-21

Updated

2026-01-21

License