【性能优化】响应式图片

117 阅读6分钟

引言

在现代 Web 开发中,图片往往占据了页面总资源的 50% 以上。在移动设备和高分辨率屏幕普及的今天,如何让用户以最小的带宽成本获得最优的视觉体验,是性能优化中的关键课题。响应式图片正是解决这一问题的核心技术方案。

手段(从手段上通过格式优化与按需加载选图)

  1. 格式优化:优先使用现代高效格式(AVIF、WebP),不支持则降级到传统格式(JPG、PNG)
  2. 按需加载:根据图片显示尺寸(槽位分段适配)/视口宽度(视口分段适配)、设备像素比(DPR)选择最合适的图片资源

作用(从作用上减少带宽浪费、提升加载速度)

  1. 减少带宽浪费:避免在小屏设备上加载过大的图片,避免在低 DPR 设备上加载高清图片
  2. 提升加载速度:更小的图片体积意味着更快的首屏渲染和更流畅的用户体验

一、槽位适配(Slot-based Adaptation)

定义:以分段适配的方式,预先提供多档尺寸、多档 DPR 的候选图片,根据图片在页面中的实际显示宽度、**设备像素比(DPR)**选择最合适的一档资源。

技术实现:通过 HTML 的 <picture> + srcset + sizes 属性实现。

核心特性

  • srcset 定义了多个候选图片地址及宽度描述符(如 image-400w.jpg 400w
  • sizes 由多组「媒体条件 + 源尺寸值」组成,描述在不同视口下的槽位宽度(如 (max-width: 600px) 100vw, 50vw
  • 浏览器根据 媒体条件源尺寸值 得出槽位宽度,再结合 设备像素比(DPR)宽度描述符 选择最合适的图片

示例代码

① 槽位分段 + 多格式(完整用法):

<picture>
  <source 
    type="image/avif" 
    srcset="/images/hero-400w.avif 400w, /images/hero-800w.avif 800w, /images/hero-1200w.avif 1200w"
    sizes="(max-width: 600px) 100vw, 50vw"
  />
  <source 
    type="image/webp" 
    srcset="/images/hero-400w.webp 400w, /images/hero-800w.webp 800w, /images/hero-1200w.webp 1200w"
    sizes="(max-width: 600px) 100vw, 50vw"
  />
  <img 
    src="/images/hero-400w.jpg" 
    srcset="/images/hero-400w.jpg 400w, /images/hero-800w.jpg 800w, /images/hero-1200w.jpg 1200w"
    sizes="(max-width: 600px) 100vw, 50vw"
    alt="Hero Image"
  />
</picture>

② DPR + 格式适配(srcsetx 描述符,picture 做格式切换):

<picture>
  <source type="image/avif" srcset="/images/hero.avif 1x, /images/hero@2x.avif 2x" />
  <source type="image/webp" srcset="/images/hero.webp 1x, /images/hero@2x.webp 2x" />
  <img 
    src="/images/hero.jpg" 
    srcset="/images/hero.jpg 1x, /images/hero@2x.jpg 2x" 
    alt="Hero Image"
  />
</picture>

二、视口适配(Viewport-based Adaptation)

定义:以分段适配的方式,结合格式选择(AVIF/WebP)、媒体查询(视口宽度、设备像素比 DPR)为每一档指定图片,浏览器根据当前视口、DPR 及格式支持情况匹配到对应的一档并加载该资源。

技术实现

  • 视口 × DPR 分段:通过 CSS 媒体查询 @media 实现
  • 格式选择(AVIF/WebP 等):依赖 JS 检测——在应用启动时探测浏览器支持情况,给 <html> 添加 .avif.webp 等 class,再由 CSS 选择器覆盖对应格式的背景图。CSS 的 @supports 对图片格式不可靠,故采用 JS + class 方案

示例代码(视口 × DPR 分段 + 格式覆盖):

① 格式检测并往 document 加 class(仅示例 AVIF):

// 用 1x1 AVIF data URI 探测,支持则在根节点加 .avif
const avifDataUri = 'data:image/avif;base64,AAAAIGZ0eXBhdmlm...'
const img = new Image()
img.onload = () => { if (img.width > 0) document.documentElement.classList.add('avif') }
img.onerror = () => {}
img.src = avifDataUri

② 视口 × DPR 分段 + 用 .avif 覆盖格式:

.hero {
  // 降级格式(JPG):视口 × DPR
  @media (width >= 0) {
    @media (resolution >= 1dppx) {
      background-image: url('/images/hero-400w.jpg');
    }
  }
  @media (width >= 768px) {
    @media (resolution >= 2dppx) {
      background-image: url('/images/hero-800w@2x.jpg');
    }
  }

  .avif & {
    @media (width >= 0) {
      @media (resolution >= 1dppx) {
        background-image: url('/images/hero-400w.avif');
      }
    }
    @media (width >= 768px) {
      @media (resolution >= 2dppx) {
        background-image: url('/images/hero-800w@2x.avif');
      }
    }
  }
}

三、槽位适配与视口适配的对比

3.1 槽位适配更细腻精确

槽位适配在媒体查询的基础上,还依据图片在页面中的实际显示宽度选图,形成“二维精准匹配”。例如:视口 1920px 时,若图片只占 50vw(960px),通过 sizes="50vw" 浏览器会选约 1000w 的图,而视口适配只能按 1920px 选图,容易造成浪费。在复杂布局(多栏、网格等)中,这种差异更明显。

3.2 适用场景不同

槽位适配适用于页面中某些槽位的显示尺寸会随视口变化而变化的场景(如多栏、网格、响应式布局中宽度不固定的图片区域)。通过 sizes 声明各视口下的槽位宽度,浏览器按实际显示宽度选图,避免大视口下小槽位仍加载大图。

视口适配则适合整体随视口缩放的场景(如全屏头图、整页背景),只按视口宽度与 DPR 分段选图,不关心图片在页面中的实际占位大小。该方案在 H5 / 小程序 等移动端页面中应用广泛。

3.3 背景图

槽位适配依赖 HTML 的 sizes 来声明“图片在不同视口下的实际宽度”。CSS 的 background-image 没有等价语法,无法描述“背景在容器中的显示宽度”,只能通过媒体查询获知视口宽度与 DPR。因此背景图无法做槽位式选图,只能采用视口分段适配。


四、响应式图片的生成思路

在工程实践中,“响应式图片”的主流生成方式并不是在构建阶段把 400w/800w/@2x/avif/webp 等变体都产出,而是:

  1. 只存一份原图:将原图上传到 OSS(对象存储),并通过 CDN 域名对外访问
  2. 按需生成变体:在 URL 上拼接 图片处理参数(query 或 path 参数),由 OSS/图片服务在请求时完成裁切/缩放/转码/质量压缩
  3. 浏览器负责选图:前端用 <picture> + srcset + sizes(槽位适配)或媒体查询(视口适配)声明候选资源,浏览器根据视口与 DPR 自动挑选最合适的一档
  4. CDN 缓存变体结果:首次请求某个参数组合会触发处理(冷启动),随后命中 CDN 缓存即可复用

优点

  • 不需要维护变体文件与 hash 映射:变体由图片服务生成并缓存
  • 构建与发布更轻:只上传原图(或少量关键变体),减少 CI 时间与产物数量
  • 策略更灵活:调整断点、质量、格式只改参数,不必重新产图

注意事项

  • 冷启动成本:某个参数组合第一次访问会触发实时处理,关键首屏图可以做预热/预请求
  • 缓存键:参数不同即缓存键不同,断点数量不宜过多(常见 3~5 档宽度)
  • 开发环境:本地开发不一定接入图片服务,通常需要降级为“直接用本地图/不加参数”