|
| 1 | +/** |
| 2 | + * 笔记图片压缩(P2-10) |
| 3 | + * |
| 4 | + * 大图(超过体积阈值)经 canvas 降采样到最长边 MAX_DIMENSION 并以 JPEG 重编码, |
| 5 | + * 控制内嵌 base64 体积,避免笔记 JSON 与 IndexedDB 膨胀;小图原样返回。 |
| 6 | + * 配合 Image 扩展的 loading="lazy" 实现懒加载,降低长笔记渲染/解码开销。 |
| 7 | + * |
| 8 | + * @ai-context: 纯客户端图像处理。压缩失败时回退原始 data URL(不阻塞插入), |
| 9 | + * 不做 blob 迁移(存量数据保持内嵌,向后兼容)。 |
| 10 | + */ |
| 11 | + |
| 12 | +/** 最长边上限(px)——超过则等比降采样 */ |
| 13 | +const MAX_DIMENSION = 1920; |
| 14 | +/** 体积阈值(字节)——小于此值原样内嵌,不压缩 */ |
| 15 | +const COMPRESS_THRESHOLD_BYTES = 300 * 1024; |
| 16 | +/** JPEG 重编码质量 */ |
| 17 | +const JPEG_QUALITY = 0.85; |
| 18 | + |
| 19 | +/** 读取 File 为 data URL */ |
| 20 | +function readAsDataURL(file: File): Promise<string> { |
| 21 | + return new Promise((resolve, reject) => { |
| 22 | + const reader = new FileReader(); |
| 23 | + reader.onload = () => resolve(reader.result as string); |
| 24 | + reader.onerror = () => reject(reader.error); |
| 25 | + reader.readAsDataURL(file); |
| 26 | + }); |
| 27 | +} |
| 28 | + |
| 29 | +/** 加载 data URL 为 HTMLImageElement */ |
| 30 | +function loadImage(src: string): Promise<HTMLImageElement> { |
| 31 | + return new Promise((resolve, reject) => { |
| 32 | + const img = new Image(); |
| 33 | + img.onload = () => resolve(img); |
| 34 | + img.onerror = () => reject(new Error('image load failed')); |
| 35 | + img.src = src; |
| 36 | + }); |
| 37 | +} |
| 38 | + |
| 39 | +/** |
| 40 | + * 压缩图片为内嵌 data URL: |
| 41 | + * - 小图(≤ 阈值)原样返回; |
| 42 | + * - 大图降采样到最长边 MAX_DIMENSION 并以 JPEG 重编码。 |
| 43 | + * 压缩失败时回退原始 data URL(不阻塞插入)。 |
| 44 | + */ |
| 45 | +export async function compressImageForNote(file: File): Promise<string> { |
| 46 | + const original = await readAsDataURL(file); |
| 47 | + if (file.size <= COMPRESS_THRESHOLD_BYTES) return original; |
| 48 | + |
| 49 | + try { |
| 50 | + const img = await loadImage(original); |
| 51 | + const scale = Math.min(1, MAX_DIMENSION / Math.max(img.width, img.height)); |
| 52 | + const targetW = Math.max(1, Math.round(img.width * scale)); |
| 53 | + const targetH = Math.max(1, Math.round(img.height * scale)); |
| 54 | + const canvas = document.createElement('canvas'); |
| 55 | + canvas.width = targetW; |
| 56 | + canvas.height = targetH; |
| 57 | + const ctx = canvas.getContext('2d'); |
| 58 | + if (!ctx) return original; |
| 59 | + ctx.drawImage(img, 0, 0, targetW, targetH); |
| 60 | + return canvas.toDataURL('image/jpeg', JPEG_QUALITY); |
| 61 | + } catch { |
| 62 | + return original; // 压缩失败回退原图 |
| 63 | + } |
| 64 | +} |
0 commit comments