图像占位符技术对比 - LQIP、BlurHash 和 SQIP 实现指南

· 9 分钟阅读

什么是图像占位符 - 为什么比空白更好

图像占位符是在实际图像加载完成前显示的轻量级预览。与空白区域或加载动画相比,占位符提供内容预期,减少感知加载时间,防止布局偏移。

为什么需要占位符:

  • 减少感知等待时间:用户看到模糊预览比看到空白更有耐心
  • 防止 CLS:占位符预留空间,图像加载后不会推移内容
  • 提供内容线索:用户可以预判图像内容,决定是否等待

三种主流方案:

  • LQIP:极小尺寸的实际图像 (如 20×15 像素),放大模糊显示
  • BlurHash:将图像编码为 20-30 字符的字符串,解码为模糊色块
  • SQIP:使用 SVG 基本图形近似图像内容

延迟加载 (lazy loading) 会把视口之外的图像推后获取,因此滚动时会出现短暂的空白。这带来三个问题:一是 CLS (Cumulative Layout Shift) 恶化——图像加载完成的瞬间布局发生跳动,Core Web Vitals 的评分随之下降;二是感知上的迟缓,读者会觉得页面还没有准备好;三是内容语境的丢失,空白区域无法提示这里原本有什么。占位符正是为了同时缓解这三点而存在。

LQIP 实现 - 使用微型图像的预览显示

LQIP (Low Quality Image Placeholder) 是最直观的方案:生成一个极小尺寸的图像版本,内联到 HTML 中,放大显示时自然产生模糊效果。

生成方法:

// Sharp 生成 20px 宽的 LQIP
const lqip = await sharp('photo.jpg')
.resize(20)
.jpeg({ quality: 50 })
.toBuffer();
const dataUri = `data:image/jpeg;base64,${lqip.toString('base64')}`;

大小:典型 LQIP 约 300-800 字节 (Base64 编码后)。可内联到 HTML 中,无需额外请求。

显示方式:使用 CSS filter: blur(20px) 增强模糊效果,配合 transform: scale(1.1) 隐藏边缘锯齿。图像加载完成后淡入替换。

优点:实现简单;保留原图的色彩分布和构图;无需额外库。缺点: Base64 编码增加 33% 体积;每张图像需要单独的 LQIP 数据。

LQIP 的做法因 Facebook 在 2015 年前后采用而广为人知。步骤有四步:①把原图缩小到宽 20-42 px (保持长宽比);②以 JPEG 质量 20-30 编码;③转成 Base64,以 data:image/jpeg;base64,... 的形式写进 <img>src;④原图加载完成后把 src 换掉。宽 20 px、质量 20 的 JPEG 大约 300-600 字节,Base64 之后约 400-800 个字符,每张图可以控制在 1KB 以内。显示时用 CSS 掩盖压缩痕迹:.lqip { filter: blur(20px); transform: scale(1.1); },加载完成后切换到 .lqip.loaded { filter: blur(0); transform: scale(1); }。缩略图本身可以用 Sharp 或 ImageMagick 在构建时批量生成。

BlurHash 工作原理 - 创新的字符串图像表示

BlurHash 将图像编码为一个短字符串 (如 "LEHV6nWB2yk8pyo0adR*.7kCMdnj"),解码后生成模糊的色彩预览。由 Wolt 公司开发。

编码原理:将图像分解为离散余弦变换 (DCT) 的低频分量。编码参数 (componentX, componentY) 控制保留的频率分量数。典型设置 (4,3) 产生约 20-30 字符的哈希。

优势:

  • 极小体积:20-30 字符,可存储在数据库字段或 JSON 中
  • 无需存储额外文件
  • 解码速度快:纯数学计算,无需图像解码器
  • 多语言支持:官方提供 TypeScript、Swift、Kotlin 等实现

实现:

import { encode } from 'blurhash';
// 编码 (服务端)
const hash = encode(imageData, width, height, 4, 3);
// 解码 (客户端)
const pixels = decode(hash, 32, 32);

局限性:无法表示锐利边缘或文字;组件数增加会使字符串变长;需要预先知道图像宽高比。

BlurHash 把系数编码成 Base83 字符串。解码分三步:①解码 Base83 得到 DCT 系数;②做逆 DCT,通常还原成 32x32 的尺寸;③转换成 sRGB 后绘制到 Canvas 或 ImageData。4x3 个分量约 20 个字符 (约 20 字节),比 LQIP 的 300-600 字节紧凑 10-30 倍。JavaScript 侧的调用只有一行:const pixels = decode('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32);,返回的是 Uint8ClampedArray (32*32*4 = 4096 个元素)。Instagram、Mastodon、Unsplash 都已采用;把这串字符放进 API 响应里,就能在图像 URL 尚未加载完成时先显示占位符。

SQIP 特点 - 使用 SVG 基本图形的艺术化占位符

SQIP (SVG-based LQIP) 使用 SVG 基本图形 (圆、椭圆、三角形等) 近似图像内容,产生具有艺术感的占位符效果。

工作原理:使用 Primitive 算法将图像分解为 N 个基本几何图形的叠加。每个图形的形状、位置、颜色和透明度通过优化确定,使组合结果尽可能接近原图。

生成方法:

npx sqip --input photo.jpg --output placeholder.svg --numberOfPrimitives 20

优势:

  • SVG 格式,无限缩放不失真
  • 视觉效果独特,具有艺术感
  • 可用 CSS 动画增强效果
  • 典型大小:800-2000 字节

劣势:

  • 生成速度慢 (每张图像数秒)
  • 不适合实时生成
  • 图形数量少时近似度低,多时文件变大
  • 维护状态不活跃

SQIP 由 Tobias Baldauf 在 2017 年发布,内部使用 Primitive (作者 Michael Fogleman)。处理流程有五步:①找出视觉上重要的区域;②放置 N 个图元 (默认 8 个);③用爬山法优化图元的形状、位置、颜色与不透明度;④对生成的 SVG 施加高斯模糊 (Gaussian blur) 滤镜;⑤转成 Base64 内联。8 个图元约 800-1200 字节。由于是矢量,在 Retina 显示屏上放大也不会出现模糊的像素块。代价是生成耗时,每张图需要 1-5 秒,图像多的站点构建时间会明显变长。此外 Node.js 的 sqip 包维护已经停滞,与较新的 Node.js 版本存在兼容性问题;替代做法是直接调用 primitive 命令行工具,再自行追加模糊滤镜。

对比与选择标准 - 为项目选择最佳技术

三种技术各有优劣,选择取决于项目的具体需求和约束。

对比表:

  • 数据大小: BlurHash (20-30 字符) < LQIP (300-800B) < SQIP (800-2000B)
  • 视觉质量: LQIP (最接近原图) > SQIP (艺术化) > BlurHash (纯色块)
  • 生成速度: BlurHash (快) > LQIP (快) > SQIP (慢)
  • 解码复杂度: LQIP (浏览器原生) < BlurHash (需要库) < SQIP (SVG 渲染)

选择建议:

  • API 驱动的应用: BlurHash。字符串可轻松存储在数据库和 API 响应中
  • 静态站点: LQIP。构建时生成,内联到 HTML,无需客户端库
  • 设计导向的站点: SQIP。独特的视觉效果作为设计元素
  • 性能极致优化: CSS 纯色背景。零额外字节,仅需提取主色调

按数据量排列:主色调 7 字节 (只有一个 CSS 颜色值) < BlurHash 20-30 字节 < LQIP 300-800 字节 < SQIP 800-1500 字节。按视觉质量排列则相反:SQIP > LQIP > BlurHash > 主色调。生成速度:主色调 (<1ms) > BlurHash (10-50ms) > LQIP (50-200ms) > SQIP (1-5 秒)。以 API 驱动的应用 (SNS、电商) 推荐 BlurHash——字符串足够短,可以直接随列表数据一起返回。

Next.js 和 Astro 集成实现 - 与现代框架的整合

将占位符技术集成到现代前端框架中的具体实现方法。

Next.js Image 组件: Next.js 的 <Image> 组件内置 placeholder="blur" 支持。静态导入的图像自动生成 LQIP;动态图像需要提供 blurDataURL

<Image src={photo} placeholder="blur" />

Next.js + BlurHash:在 getStaticProps 中计算 BlurHash,通过 props 传递到组件。客户端使用 react-blurhash 或 Canvas API 解码显示。

Astro 集成: Astro 的 <Image> 组件支持通过 Sharp 在构建时生成 LQIP。自定义集成可在 Markdown 图像上自动添加占位符。

通用实现模式:

  • 构建时:遍历所有图像,生成占位符数据,写入 JSON 或内联到 HTML
  • 运行时:先显示占位符,监听图像 onload 事件,加载完成后淡入切换
  • CSS 过渡:transition: opacity 0.3s 实现平滑切换

Next.js 的 <Image> 通过 placeholder prop 原生支持 LQIP 与 BlurHash。动态图像可以在服务端用 plaiceholder 库生成:const { base64 } = await getPlaiceholder('/photo.jpg');,把得到的 base64 传给 placeholder 即可。Astro 没有直接的占位符支持,通常在构建时用 Sharp 处理:const buffer = await sharp('src/images/photo.jpg').resize(20).jpeg({ quality: 20 }).toBuffer();,再用 buffer.toString('base64') 内联。React 侧可以使用 react-blurhash,与 Intersection Observer 组合,在元素进入视口时再加载真实图像。内联的 Base64 会让 HTML 变大,所以 SSR 页面要留意初始 HTML 的传输量:一页有 50 张以上图像时,只对首屏附近的 5-10 张使用 LQIP,其余用主色调,是 Web 性能上的平衡点。

相关文章

图像懒加载实现指南 - loading=lazy 与 IntersectionObserver 的选择

系统讲解图像懒加载的实现方法。对比原生 loading=lazy 和 IntersectionObserver 方案,涵盖性能优化和最佳实践。

图像加载策略设计 - 掌握 preload、fetchpriority 和 decoding

系统讲解图像加载策略的设计。涵盖 preload 预加载、fetchpriority 优先级控制、decoding 解码策略和综合加载方案。

使用 Data URI 嵌入图像 - Base64 编码原理与最佳实践

了解 Data URI 方案如何将图像直接嵌入 HTML/CSS。深入理解 Base64 编码原理、性能影响、适用场景以及何时应避免使用内联图像。

图片画廊性能优化 - 大量图像的高效加载与渲染

系统讲解图片画廊的性能优化技术。涵盖虚拟滚动、懒加载策略、缩略图生成、内存管理和流畅滚动体验。

Web 图像性能审计 - Core Web Vitals 改善实践指南

Web 图像性能审计的完整方法论。涵盖审计工具与指标、LCP 优化、CLS 防止、传输大小优化及持续监控体系。

SVG 基础与实用技巧 - 从矢量基础到动画

SVG 的基础知识与实用技巧完全指南。涵盖坐标系统、路径命令、CSS/JavaScript 操作、动画实现及性能优化。

相关术语