跳到主要内容

缩略图与离屏导出

列表封面、分享图、打印 PDF 插图,都是 离线光栅任务。不要借用页面上正在交互的那张 canvas toDataURL

1. 规格

export interface RasterSpec {
width: number;
height: number;
dpr: number;
mime: "image/png" | "image/jpeg" | "image/webp";
quality?: number;
background?: string;
}

export async function rasterScene(
paint: (ctx: OffscreenCanvasRenderingContext2D, spec: RasterSpec) => void,
spec: RasterSpec,
): Promise<Blob> {
const canvas = new OffscreenCanvas(
Math.max(1, Math.round(spec.width * spec.dpr)),
Math.max(1, Math.round(spec.height * spec.dpr)),
);
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("2d unavailable");
if (spec.background) {
ctx.fillStyle = spec.background;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.setTransform(spec.dpr, 0, 0, spec.dpr, 0, 0);
paint(ctx, spec);
return canvas.convertToBlob({
type: spec.mime,
quality: spec.quality,
});
}

同一份 paint(scene) 给视口和导出用。导出时 camera 设成 fit,而不是截当前用户随便缩放到的视口——除非产品就是「导出所见」。

多规格(16:9 封面、1:1 头像、A4 300dpi):循环 rasterScene,不要先导出一张再 CSS 拉伸当另一种。

2. Worker

主线程只发 scene JSON + 已 transfer 的 ImageBitmap。Worker 里 rasterScene。队列化:列表一次性 200 张缩略图要限并发(2–4),否则解码 + 光栅把内存打满。

Worker 没有 DOM font 时,fillText 只能用已加载的字体。复杂中文标题要么主线程导出,要么 OffscreenCanvas 字体加载探测失败就降级系统字体并打日志。

3. 打印 DPI

export function a4Px(dpi: number): { width: number; height: number } {
const inchW = 8.27;
const inchH = 11.69;
return {
width: Math.round(inchW * dpi),
height: Math.round(inchH * dpi),
};
}

屏幕预览 96dpi 观感不能当印刷。导出 300dpi A4 约 2480×3508,再 × 业务 dpr 会 OOM。印刷导出 dpr=1,尺寸直接用英寸×dpi。

window.print() 打 canvas 依赖浏览器把 backing store 送到打印面,模糊且不可控。要印就导出 PDF/PNG 让用户下。

4. 失败形态

症状原因
缩略图和编辑器字体不同Worker 缺字体
列表滚动卡死主线程同步 toDataURL
JPEG 透明变黑mime 选错
印刷发糊用了屏幕 canvas 截图
内存崩溃200 张 4k bitmap 没 close

权威资料

核对日期:2026-08-26