概述

客户端媒体处理利用 WebAssembly 在浏览器中处理图像压缩、调整大小、格式转换、旋转和缩略图生成,而不是在服务器上。它在 WordPress 7.1 中默认启用(针对支持的浏览器),并在不可用时透明地回退到服务器端处理。

本指南涵盖插件和主题开发人员如何与客户端媒体处理进行交互、自定义和故障排除。

深入了解架构,请参阅 客户端媒体处理架构。

要求

仅当浏览器、设备和网络均满足以下阈值时,才会激活客户端媒体处理。必须通过所有检查;如果任何一项失败,WordPress 将透明地回退到服务器端处理,且不会向用户显示任何变化。

浏览器

该管道依赖于 Document-Isolation-Policy (DIP) 来启用 SharedArrayBuffer,目前仅限于基于 Chromium 的浏览器。

浏览器最低版本备注
Chrome137+通过 Document-Isolation-Policy 完全支持。
Edge137+通过 Document-Isolation-Policy 完全支持。
Firefox—不支持(无 Document-Isolation-Policy 支持)。
Safari—不支持 WASM 管道;HEIC 画布回退仍然有效。

浏览器功能

除了支持的浏览器外,还必须提供以下 API:

  • WebAssembly — wasm-vips 所需。
  • SharedArrayBuffer — WASM 线程所需(意味着通过 DIP 的跨源隔离已激活)。
  • Web Worker — 用于非主线程 vips worker 所需。
  • CSP 允许 blob: workers — worker-src 指令必须包含 blob:(请参阅 内容安全策略要求)。

设备和网络

要求阈值原因
设备内存> 2 GBWASM 图像处理会在内存中保留完整图像和工作缓冲区;报告 ≤ 2 GB RAM 的设备将被排除,以避免内存溢出崩溃。
CPU 核心≥ 2WASM worker 在编码期间可能独占一个核心;至少两个核心可保持 UI 线程响应。
网络连接非 2g/slow-2g,关闭 Save-Data约 13 MB 的 worker 下载受限于更快的连接;允许 3g 及以上。

注意: HEIC/HEIF 解码使用单独的、较宽松的网关。它只需要 createImageBitmap 和 OffscreenCanvas,因此 Safari 可以转换 iPhone 照片,即使它无法运行 WASM 管道。请参阅 支持的文件格式。

有关完整检查列表和确切检测逻辑,请参阅 架构文档。

禁用客户端媒体处理

使用 wp_client_side_media_processing_enabled 过滤器来禁用客户端处理。

禁用整个站点

add_filter( 'wp_client_side_media_processing_enabled', '__return_false' );

条件禁用

add_filter( 'wp_client_side_media_processing_enabled', 'example_disable_for_non_admins' );

function example_disable_for_non_admins( $enabled ) {
    if ( ! current_user_can( 'manage_options' ) ) {
        return false;
    }
    return $enabled;
}

禁用后,所有图像处理将回退到传统的服务器端管道。无需其他更改。

自定义图像处理设置

客户端处理尊重现有的 WordPress 图像过滤器。这些过滤器通过 REST API 索引从服务器读取,并在客户端处理期间应用。

大图像尺寸阈值

big_image_size_threshold 过滤器控制上传图像的最大维度。大于此阈值的图像将被缩小。默认值为 2560 像素。

// 将大图像阈值更改为 1920px。
add_filter( 'big_image_size_threshold', function () {
    return 1920;
} );

无论处理是发生在客户端还是服务器端,其工作方式都相同。

图像输出格式转换

image_editor_output_format 过滤器控制自动格式转换(例如,将 JPEG 上传转换为 WebP)。

// 将所有 JPEG 上传转换为 WebP。
add_filter( 'image_editor_output_format', function ( $formats ) {
    $formats['image/jpeg'] = 'image/webp';
    return $formats;
} );

客户端处理将在上传子尺寸图像之前应用此转换。如果 PNG 具有透明度且目标格式为 JPEG,则跳过转换以保留 alpha 通道。

UltraHDR JPEG(带有嵌入式 HDR 增益图的照片——Google 的 UltraHDR、Apple 的 Adaptive HDR 和 Adobe 的工具支持的向后兼容的 HDR 方法)也被排除在格式转换之外——转换为不同的编解码器会剥离增益图。它们以上传未修改的形式上传,其子尺寸生成具有增益图保留的 UltraHDR JPEG。有关详细信息,请参阅 架构文档。

渐进式/交错图像输出

image_save_progressive 过滤器控制是否以渐进式(JPEG)或交错(PNG、GIF)编码保存图像。

// 启用渐进式 JPEG 输出。
add_filter( 'image_save_progressive', function ( $interlaced, $mime_type ) {
    if ( 'image/jpeg' === $mime_type ) {
        return true;
    }
    return $interlaced;
}, 10, 2 );

元数据剥离

image_strip_meta 过滤器控制是否从生成的图像中剥离元数据。默认情况下,生成的子尺寸仅保留颜色配置文件(对于 UltraHDR 还包括增益图),与服务器端行为匹配。返回 false 可保留生成图像上的所有元数据(EXIF、XMP、IPTC):

// 在生成的子尺寸上保留所有元数据。
add_filter( 'image_strip_meta', '__return_false' );

最大位深度

image_max_bit_depth 过滤器限制生成图像的位深度。默认情况下,高位深度的 AVIF 源(10 或 12 位,HDR 照片常见)在生成的子尺寸中保留其位深度;较低的限值会降低文件大小,但会牺牲动态范围:

// 将生成的图像限制为每通道 8 位。
add_filter( 'image_max_bit_depth', function () {
    return 8;
} );

客户端会将限值调整为 AVIF 编码器支持的深度(8、10 或 12 位)。

支持的文件格式

客户端处理在 WASM/vips 管道中处理以下 MIME 类型:image/jpeg、image/png、image/gif、image/webp、image/avif。此集合之外的文件将根据类型落入两条路径之一:

  • HEIC/HEIF (image/heic, image/heif):客户端使用 createImageBitmap、HTMLImageElement/OffscreenCanvas 策略或 HEIC 容器 + WebCodecs VideoDecoder 路径(Chromium 107+ 通过平台编解码器——macOS 上的 VideoToolbox,Windows 上的 Microsoft HEVC Video Extension)解码文件,导出 JPEG (.jpg, MIME image/jpeg),并让服务器从 JPEG 生成子尺寸。原始 HEIC 作为伴生文件保留在 $metadata['original'] 中,并在附件删除时一并删除。
  • 其他(视频、音频、文档):文件以上传默认参数直接上传到服务器并进行标准服务器端处理。不运行客户端步骤。

适用于 WASM 管道的 MIME 类型集合在 packages/upload-media/src/store/constants.ts 中的 CLIENT_SIDE_SUPPORTED_MIME_TYPES 处固定——没有公共过滤器来添加或删除类型。

动态 GIF 转视频转换

当上传不透明的动态 GIF 时,编辑器将其转换为伴生 MP4(或 WebM)视频。图像块上的“作为视频显示”工具栏控件然后允许用户将块切换到类似于原始 GIF 播放的视频块的"GIF"变体。这大幅减少了下载大小,同时保留了动态 GIF 的外观和感觉。转换通过浏览器的 WebCodecs API 和 @wordpress/video-conversion 包在客户端发生;有关完整管道,请参阅 架构文档。

插件和主题开发人员应了解:

  • 附件保持为 GIF 格式。上传的 GIF 在媒体库中仍作为单个 image/gif 附件存在。转换后的视频和首帧海报被存储为配套文件,记录在 media_details.animated_video 和 media_details.animated_video_poster 中——它们永远不会成为独立的附件。当 GIF 附件被删除时,这些文件会自动移除。
  • 前端渲染真实的视频块。切换是由用户在编辑器中主动触发的块切换,而非渲染时的过滤器。发布的内容是原生的 core/video 块(<video autoplay loop muted playsinline poster>),因此无需针对 GIF 进行任何输出过滤处理。
  • 透明 GIF 不会被转换。由于 <video> 无法还原 GIF 的透明度,因此透明动画 GIF 上传时作为普通图片处理,且不生成配套文件。
  • 这是可选且可逆的功能。块永远不会自动切换——用户通过图像块的“显示为视频”工具栏控件进行转换,而结果视频块上的“显示为 GIF"控件则将其切换回原始 GIF。
  • 图库、媒体与文本以及封面不受影响。只有独立的图像块提供切换功能;图库内的 GIF 保持原样,媒体与文本/封面中的媒体文件也保持不变。
  • 浏览器支持。转换需要 WebCodecs 视频编码功能(ImageDecoder + VideoEncoder)。不支持该功能的浏览器(尤其是 Firefox)会透明地上传原始 GIF,而不会报错。

没有公开的过滤器专门让站点退出 GIF 转换;完全禁用客户端媒体处理(参见 禁用客户端媒体处理)也会同时禁用此功能。

与上传存储库协作(JavaScript)

客户端媒体处理由 core/upload-media 数据存储管理。插件开发者可以使用其公开 API 来监控和交互上传操作。

检查上传状态

import { select } from '@wordpress/data';
import { store as uploadStore } from '@wordpress/upload-media';

// Check if any upload is in progress.
const uploading = select( uploadStore ).isUploading();

// Get all items in the upload queue.
const items = select( uploadStore ).getItems();

向上传队列添加项目

import { dispatch } from '@wordpress/data';
import { store as uploadStore } from '@wordpress/upload-media';

dispatch( uploadStore ).addItems( {
    files: [ file1, file2 ],
    onSuccess( attachment ) {
        console.log( 'Uploaded:', attachment.id );
    },
    onError( error ) {
        console.error( 'Upload failed:', error.message );
    },
    onBatchSuccess() {
        console.log( 'All files in batch uploaded.' );
    },
    additionalData: {
        post: postId,
    },
} );

可用选择器

选择器返回类型描述
isUploading()boolean是否有任何上传正在进行中。
isUploadingById( id )boolean特定附件 ID 是否正在上传。
isUploadingByUrl( url )boolean特定 URL 是否正在上传。
getItems()array上传队列中的所有项目。
getSettings()object当前上传设置(并发限制、允许的类型等)。

可用操作

操作描述
addItems( args )向上传队列添加文件。接受 files、onChange、onSuccess、onBatchSuccess、onError、additionalData、allowedTypes。
cancelItem( id, error )取消正在进行的上传并清理资源。
retryItem( id )重试失败的上传。

服务器端插件兼容性

当客户端处理处于激活状态时,wp_generate_attachment_metadata 过滤器以与服务器端上传相同的方式触发:在初始上传期间带 'create' 上下文触发一次,并在 WordPress 调用最终化端点(POST /wp/v2/media/{id}/finalize)后再次触发,此时所有客户端子尺寸侧载已完成。

依赖此钩子进行水印添加、CDN 同步、自定义图像尺寸或其他后期处理的插件无需修改即可继续工作——请确保它们以幂等方式编写,以便正确处理这两次调用。

这种双重触发模式与 WordPress 在服务器端处理大图像上传时使用的模式相同,其中子尺寸生成被延迟并触发第二次 'update' 传递。已经适配大图像上传的插件无需更改即可兼容此模式。

如果最终化请求失败,错误会被记录但上传仍会成功——最终化是尽力而为的,以确保插件故障不会阻止用户上传。

客户端上传期间触发的钩子

客户端上传作为一系列 REST 请求到达服务器,每个请求都会触发标准钩子(所有支持的图像类型的完整审计见 #80210):

  1. 创建(POST /wp/v2/media):触发通常的上传流程——wp_handle_upload_prefilter、wp_check_filetype_and_ext、wp_handle_upload、add_attachment、rest_insert_attachment / rest_after_insert_attachment、wp_read_image_metadata 以及 wp_generate_attachment_metadata(上下文为 'create')。
  2. 侧载(POST /wp/v2/media/{id}/sideload,每个生成的文件一次):对于每个客户端生成的子尺寸,wp_handle_upload_prefilter 和 wp_handle_upload 都会触发,并附带最终文件路径。需要查看每个生成文件的插件(卸载器、优化器)会针对每个文件恰好收到一次 wp_handle_upload 调用。配套文件(HEIC 原始文件、动画 GIF 的视频和海报)会通过相同的端点并触发相同的钩子。
  3. 最终化(POST /wp/v2/media/{id}/finalize):wp_generate_attachment_metadata(上下文为 'update')会携带完整的 sizes 数组触发——这是“一切已完成”的信号。

不再适用的钩子

由于解码、缩放和编码在浏览器中完成,不会实例化服务器端的 WP_Image_Editor,因此以下三个钩子在客户端路径上永远不会触发:

  • wp_image_editors——没有可替换的服务器端编辑器。如果您的站点依赖自定义图像编辑器实现,请使用 wp_client_side_media_processing_enabled 过滤器禁用客户端处理。
  • image_make_intermediate_size——使用上述每个侧载触发的 wp_handle_upload 来查看生成的文件,并在最终化传递中获取完整元数据。
  • image_memory_limit——PHP 内存不参与客户端处理。

其他图像处理过滤器(image_editor_output_format、wp_editor_set_quality、jpeg_quality、big_image_size_threshold、image_save_progressive、image_strip_meta、image_max_bit_depth)继续正常工作:它们的值在服务器端读取并发送给客户端,客户端在处理时会遵循这些设置。参见 自定义图像处理设置。

EXIF 旋转

wp_image_maybe_exif_rotate 仍会对携带 EXIF 方向信息的图像触发,但在客户端上传期间服务器会强制将其值设为 false:客户端会使用上传响应中的 exif_orientation 字段自行应用旋转。

控制图像质量

客户端编码遵循与服务器端图像质量相同的 PHP 过滤器——wp_editor_set_quality,以及针对 JPEG 输出的 jpeg_quality。过滤后的值会包含在上传响应的尺寸感知 image_quality 字段中,因此调整服务器端质量的代码无需更改即可正常工作:

/*
 * Size-aware: drop JPEG thumbnails (300px wide or less) to quality 60,
 * leave larger sizes untouched.
 */
add_filter(
    'wp_editor_set_quality',
    function ( $quality, $mime_type, $size ) {
        if ( 'image/jpeg' === $mime_type && isset( $size['width'] ) && $size['width'] <= 300 ) {
            return 60;
        }
        return $quality;
    },
    10,
    3
);

Quality 使用 WordPress 1–100 评分标准;客户端将其转换为 vips worker 期望的 0–1 比例,并在调整大小和转码期间为每个子尺寸应用该比例。当服务器未报告该字段时,客户端回退到默认值 0.82。没有单独的 JavaScript quality 过滤器。

使用 finalize 端点

在所有客户端缩略图侧载完成后,finalize 端点会触发服务器端后处理:

POST /wp/v2/media/{id}/finalize

此端点应用 wp_generate_attachment_metadata 过滤器,上下文为 'update',然后保存更新的元数据。它需要 edit_post 和 upload_files 权限。这是客户端上传期间的两次传递中的第二次——有关完整情况,请参阅 服务器端插件兼容性。

WordPress 将其作为客户端上传管道的一部分自动调用此端点。插件开发人员无需手动调用它——此处记录它是为了说明如何保留服务器端钩子。

跨源隔离注意事项

客户端媒体处理需要 SharedArrayBuffer 用于 WASM 线程。WordPress 使用 Document-Isolation-Policy (DIP) 在区块编辑器屏幕上自动启用此功能,它为每个文档提供跨源隔离,而不会影响页面上的其他 iframe。

对插件的影响

  • 外部资源:从其他来源加载的脚本、样式、图像、音频和视频无需 crossorigin 属性即可继续工作。isolate-and-credentialless 在没有凭据的情况下加载它们,而不是阻止它们,因此受签名 cookie 或登录会话保护的资源是唯一会出问题的情况。WordPress 不会向它们添加 crossorigin="anonymous",因为这会强制进行 CORS 请求,而缺少 Access-Control-Allow-Origin 头的主机会失败。
  • 第三方页面构建器:对于带有非 edit 的 action 参数的管理页面,DIP 会被跳过,以避免与依赖同源 iframe 访问的页面构建器发生冲突。
  • 外部图像:将外部图像导入媒体库是在服务器端处理的——编辑器将图像 URL 发送到 POST /wp/v2/media(url 参数),然后服务器下载并侧载文件。浏览器跨源获取会受到 CORS 限制,并在 credentialless 隔离文档中失败,因此导入远程媒体的插件应使用相同的服务器端路径(对编辑器暴露为 mediaSideloadFromUrl 设置),而不是在浏览器中使用 fetch() 获取图像字节。

内容安全策略 (CSP) 要求

如果您的站点或插件设置了内容安全策略,worker-src 指令必须包含 blob: 以允许内联 worker 创建:

Content-Security-Policy: worker-src 'self' blob:;

如果没有此设置,WASM 处理 worker 无法创建,客户端处理将回退到服务器端。

检查隔离状态

您可以在浏览器控制台中验证跨源隔离是否处于活动状态:

console.log( window.crossOriginIsolated ); // true if isolation is active

使用侧载端点

侧载端点允许将预处理的图像变体(缩略图、缩放版本)上传到现有附件:

POST /wp/v2/media/{id}/sideload

参数

ParameterTypeRequiredDescription
image_sizestring | string[]Yes图像尺寸名称(例如,thumbnail、medium、large、scaled、original)。传递数组以在共享尺寸的多个尺寸下注册单个物理文件。
generate_sub_sizesbooleanNo服务器是否应从此上传生成额外的子尺寸。默认 true。
convert_formatbooleanNo是否通过 image_editor_output_format 应用服务器端格式转换。默认 true。
replace_filebooleanNo当为 true 时,用上传的文件替换附件的主文件,更新 MIME 类型和元数据并删除旧文件。默认 false。由 HEIC → JPEG 配套路径使用。

示例

const formData = new FormData();
formData.append( 'file', thumbnailBlob, 'image-150x150.webp' );

await wp.apiFetch( {
    path: `/wp/v2/media/123/sideload?image_size=thumbnail`,
    method: 'POST',
    body: formData,
} );

此端点需要 edit_post 和 upload_files 权限。

尺寸验证

该端点检查上传文件的尺寸是否适合您声明的 image_size。original 上传必须与附件存储的尺寸完全匹配;常规注册尺寸(thumbnail、medium 等)不得超过该尺寸的注册宽度或高度(带 1px 舍入容差);并且每个尺寸都必须具有正尺寸。scaled、full 和 original-heic 尺寸免于最大尺寸检查。如果验证失败,上传的文件将被丢弃,请求将返回带有 rest_upload_dimension_mismatch、rest_upload_invalid_dimensions 或 rest_upload_unknown_size 之一的 400 响应。

故障排除

ProblemCauseSolution
客户端处理未激活浏览器缺少 Document-Isolation-Policy 支持客户端处理需要 Chrome/Edge 137+。在浏览器控制台中检查 window.crossOriginIsolated。
Chrome 137+ 上未激活客户端处理Document-Isolation-Policy 头未发送验证您正在编辑帖子或页面(该头在带有非 edit 的 action 的管理页面上被跳过)。某些第三方页面构建器可能会抑制该头。
CSP 阻止 worker 创建worker-src 指令过于严格向 worker-src CSP 指令添加 blob::worker-src 'self' blob:
处理在具备能力的浏览器上回退服务器端禁用功能检查 wp_client_side_media_processing_enabled 过滤器是否未返回 false。
大图像导致浏览器变慢设备内存不足RAM ≤ 2 GB 的设备会自动排除。考虑降低您站点的大图像尺寸阈值。
功能在低规格笔记本电脑上禁用低于 2 CPU 核心或 2 GB RAM这些阈值保护性能不足的设备免受内存溢出崩溃,且不可配置。受影响的设备回退到服务器端处理。
功能在慢速网络上禁用2g/slow-2g 连接或 Save-Data 已开启~13 MB worker 下载受 effectiveType 和 saveData 限制。3g 及更快的连接被允许。
在不支持 HEIC 的服务器上出现 HEIC 上传错误服务器端缺少 HEIC 解码器客户端在上传前将 HEIC 转换为 JPEG——这不应发生在匹配 isHeicCanvasSupported() 的浏览器中。验证浏览器提供 createImageBitmap 和 OffscreenCanvas。
上传以“图像转码错误”失败不支持的格式或损坏的文件验证文件是否为支持的格式(JPEG、PNG、WebP、AVIF、GIF、HEIC)。
保存草稿按钮保持禁用锁未释放确保所有上传项目已完成或被取消。当上传队列为空时,锁会释放。
侧载请求以 400 尺寸错误被拒绝上传的变体与声明的 image_size 不匹配侧载端点验证尺寸。确保文件匹配 image_size 中的尺寸:original 的精确匹配,或对于命名尺寸(如 thumbnail)在注册的范围内。
插件的图像处理钩子停止运行钩子在客户端路径上不触发wp_image_editors、image_make_intermediate_size 和 image_memory_limit 在浏览器中处理时永远不会触发。有关替代方案,请参阅 不再适用的钩子。

调试 WASM 处理器

WASM 图像处理器 (wasm-vips) 写入内部 stdout/stderr 消息(例如,不可操作的 AVIF 编解码器警告),默认情况下被抑制以保持浏览器控制台整洁。要在调试时捕获此输出,在上传前将函数分配给 globalThis.__vipsDebug:

globalThis.__vipsDebug = ( text ) => console.log( '[vips]', text );

完成后将其重置为 undefined。