搜索全站
性能优化 12 分钟阅读

WordPress 自动转 WebP 兼容旧浏览器:picture 与 CDN 方案

站点换成 WebP 后旧浏览器打不开图?本文讲清 WebP 兼容的底层逻辑,对比 picture 标签、服务器改写、CDN 实时转换三条路线,并给出可直接用的代码与验证命令。

U
UFO_PF 2023-10-29 · 阅读 12 分钟
Wo 性能优化
阅读提示
本文约 4,527 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:一张图在两种浏览器里命运不同

把全站图片换成 WebP,体积通常能再降 25%–35%,对首屏 LCP 的收益非常直接。但真实环境里总会遇到「用户说图片全裂了」的反馈:一部分旧版 Safari、部分安卓 WebView、企业内部套壳浏览器,并不支持 WebP。如果只在服务端把 .jpg 换成 .webp 而不做任何协商,这些用户看到的就是一片空白。

所以 WebP 落地的关键不是「能不能转」,而是「怎么让支持的浏览器拿到 WebP,不支持的浏览器自动拿回 JPEG/PNG」。这件事有三条技术路线:用 <picture> 在 HTML 层做多源声明、在服务器层按 Accept 请求头改写、交给 CDN 实时转换。三条路线的成本、兼容性和对缓存的影响完全不同,选错一条,后面全是返工。

原理分析:协商发生在哪一层

浏览器发起图片请求时会带上 Accept 请求头,支持 WebP 的浏览器会包含 image/webp(Chrome、Edge、Firefox、Safari 14 及以上都支持)。服务端或 CDN 看到这个标记,就返回 WebP;看不到,就返回原格式。这是最本质的判断依据,三条路线的区别只在于「谁来读这个头」。

方案 判断位置 优点 代价
<picture> 多源 浏览器自己选 无需服务器配置,兼容性最好 要改主题输出,后台/富文本里的图不一定覆盖到
服务器改写 Nginx/Apache 读 Accept 对 HTML 零侵入 动态改写配置复杂,缓存要带 Vary: Accept
CDN 实时转换 CDN 边缘节点 不改站点、不改 HTML 依赖 CDN 能力,回源与刷新逻辑要理清

还有一个容易被忽略的前提:WordPress 本身从 5.8 起支持上传 WebP 图片(mime 类型为 image/webp),并提供了 image_editor_output_format 过滤器,让你可以指定「生成缩略图时输出什么格式」。也就是说,生成 WebP 这一步 WordPress 已经能自己做,不需要插件也行——但兼容旧浏览器这件事,它管不了。

保姆级实操:从生成到输出一次做完

步骤1:确认服务器图片库支持 WebP。 用命令行看 GD 或 Imagick 是否具备 WebP 能力,也可以在后台「工具 → 站点健康」里查看相关条目。

php -r 'var_dump( extension_loaded("gd"), function_exists("imagewebp"), extension_loaded("imagick") );'
# 若使用 libwebp 命令行工具,可单独验证
cwebp -version

步骤2:让新上传图片自动生成 WebP 缩略图。 利用 WordPress 5.8+ 的 image_editor_output_format 过滤器,把 JPEG 的输出格式改成 WebP。注意这会影响所有缩略图尺寸,主题里若有硬编码 .jpg 后缀的逻辑需要同步调整。

<?php
// 主题 functions.php 或 mu-plugin:缩略图统一输出 WebP
add_filter( 'image_editor_output_format', function ( $formats ) {
    $formats['image/jpeg'] = 'image/webp';
    $formats['image/png']  = 'image/webp';
    return $formats;
} );

步骤3:把历史图片批量转换。 新图走过滤器,旧图需要单独处理。可以用插件(WebP Express、Converter for Media 都是成熟的本地方案),也可以用 cwebp 自己跑,后者对大站更可控。

# 批量把 uploads 里的 jpg 转成同名 .webp(保留原文件)
find wp-content/uploads -type f -iname '*.jpg' -print0 | while IFS= read -r -d '' f; do
  [ -f "${f%.jpg}.webp" ] || cwebp -quiet -q 80 "$f" -o "${f%.jpg}.webp"
done

步骤4:用 <picture> 输出双源。 如果你能改主题模板,这是兼容性最稳的做法:把不支持 WebP 的浏览器交给 <img> 兜底。下面用 WordPress 6.0+ 的 wp_content_img_tag 过滤器,把正文里的图片自动包成 <picture>,避免逐篇改文章。

<?php
// 正文图片自动包一层 picture,WebP 优先,原图兜底
add_filter( 'wp_content_img_tag', function ( $html, $context, $attr ) {
    if ( empty( $attr['src'] ) ) {
        return $html;
    }
    $src  = $attr['src'];
    $webp = preg_replace( '/\.(jpe?g|png)$/i', '.webp', $src );
    if ( $webp === $src ) {
        return $html;
    }
    return sprintf(
        '<picture><source type="image/webp" srcset="%1$s">%2$s</picture>',
        esc_url( $webp ),
        $html
    );
}, 10, 3 );

步骤5:服务器层改写(可选)。 若不想动 HTML,可以在 Nginx 里按 Accept 映射后缀。这种做法对缓存最敏感,务必让缓存以 Vary: Accept 区分,否则会出现「给不支持的浏览器返回了 WebP」的经典事故。

map $http_accept $webp_suffix {
    default   "";
    "~*webp"  ".webp";
}
server {
    location ~* ^/wp-content/uploads/.+\.(png|jpe?g)$ {
        add_header Vary Accept;
        try_files $uri$webp_suffix $uri =404;
    }
}

步骤6:CDN 方案取舍。 使用 CDN 的实时转换(如 Cloudflare 的 Polish / Image Resizing、ShortPixel Adaptive Images、Optimole 一类服务)时,站点本身几乎不用改,图片甚至不需要真的存在 .webp 文件。代价是:图片 URL 变成了 CDN 域名的地址,你要额外处理防盗链、回源鉴权和缓存刷新。选择本地方案还是 CDN,取决于你愿不愿意把图片处理链交给第三方——外贸站常年挂 CDN,通常直接走边缘转换更省事。

步骤7:验证。 用 curl 模拟两种浏览器请求头,确认返回的内容类型确实不同。

# 模拟支持 WebP 的浏览器
curl -sI -H 'Accept: image/webp,image/*,*/*' https://example.com/wp-content/uploads/demo.jpg | grep -i 'content-type\|content-length'
# 模拟不支持 WebP 的浏览器
curl -sI -H 'Accept: image/*,*/*' https://example.com/wp-content/uploads/demo.jpg | grep -i 'content-type\|content-length'

常见踩坑

  1. Vary: Accept 缺失导致串图。CDN 或 Nginx 缓存只按 URL 存,不支持 WebP 的用户拿到了 WebP,图片直接裂开。加上 Vary: Accept 是最低要求。
  2. CSS 背景图无法用 <picture>。background-image 没有多源机制,只能靠服务器改写或 CDN 转换覆盖,别指望 picture 方案一劳永逸。
  3. 开启 image_editor_output_format 后老主题报错。某些老主题在模板里拼接 .jpg 后缀取图,缩略图实际已是 .webp,会出现 404 或空白。
  4. AVIF 与 WebP 混用要谨慎。AVIF 压缩率更高但编码慢、兼容性更窄,不要一次性全站切 AVIF。可以先看ShortPixel/Imagify/Smush 图片压缩插件对比里各插件的格式支持再决定。
  5. 微信内置浏览器与部分 App WebView 行为不一致。个别 WebView 的 Accept 头不带 image/webp,却又能解码 WebP,导致「明明能显示却拿到 JPEG」,属于可接受降级,不必强行修正。
  6. 案例:某站点用 Nginx map 做 WebP 改写,CDN 回源时把 Accept 头统一改写成了 */*,边缘节点缓存了一份 JPEG,结果全站用户(包括 Chrome)全部拿到 JPEG,WebP 白做半个月没人发现。排查方式是直接 curl 边缘节点 URL 对比 content-type,最终在 CDN 侧开启「按 Accept 头区分缓存」才解决。
  7. 富文本之外的图片漏改。特色图、WooCommerce 产品图、页面构建器模块图往往不走 the_content,用正文过滤器是覆盖不到的,需要单独处理对应钩子。

总结

判断该走哪条路线,只看两个条件:能不能改主题和有没有 CDN。能改主题、图片主要在正文里,<picture> 最稳妥;有成熟 CDN,直接开边缘转换,站点几乎零改造;两者都没有,再考虑 Nginx/Apache 改写,但一定要把 Vary: Accept 配好。生成 WebP 本身不难,难的是「协商」和「缓存」这两步,绝大多数线上事故都出在这两步上。落地后记得用 curl 双请求头验证,并用真实旧设备抽查一次。相关延伸可看用插件实现图片 CDN 加速与自适应尺寸。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。