如果内容库后台用的是 xhEditor,运营从 PPT 复制内容粘贴进来,图片自动压缩绝对是个救命需求。我印象里最狠的一次,同事从一份 100MB 的 PPT 里复制了几页版式到编辑器,页面居然卡了大概十秒,后端保存接口直接超时,查日志发现光粘贴进去的图片 base64 就有二十多兆。那之后我就在 xhEditor 上做了导入 PPT 图片自动压缩的逻辑,前前后后跑了大半年,今天把方案和坑一起整理出来。
先把场景定义清楚,这里说的“导入 PPT”,不是让你在后台里选一个 .pptx 文件上传并解析,而是大多数人真正的用法:把 PPT 页面里的文字、图形、截图通过系统粘贴板直接 Ctrl+V 进 xhEditor。这类内容在复制时会把图片变成很长一段 base64 的 dataURL,如果不处理,轻则保存卡顿,重则直接超时。这篇文章会把浏览器粘贴 PPT 的底层机制、Canvas 压缩方案、xhEditor 的接入代码和我在生产环境踩过的坑一次讲透,适合正在维护老后台系统、用 xhEditor 做富文本编辑的同学参考。
1. 先搞明白:PPT 粘贴进编辑器后,图片为什么能撑爆请求
1.1 PPT 的复制机制决定了它会产出高分辨率大图
PPT 并不是一个图片编辑器,它的页面里往往混合了文字框、矢量形状、位图、图表、SmartArt 这些元素。当你从 PPT 里复制一整块内容,再粘贴到 html 编辑器时,浏览器要拿到的是 HTML 片段,PPT 会先把你选中的内容“拍扁”成一张位图,再以图片形式放进剪贴板的 HTML 数据里。
这相当于 PPT 替你做了一次整页截图。截图的分辨率不是屏幕上的 1:1,而是跟着 PPT 画布大小、缩放比例、系统 DPI 甚至 Retina 适配走的。常见 16:9 的幻灯片,复制出来可能是 1920x1080,如果对方用的是高分辨率显示器或 PPT 被放大显示过,复制出来的图有可能是 2560x1440 甚至更高。
图片本身的体积也会非常大。PPT 为了保持图形的阴影、渐变、透明度这些效果,导出位图时通常选择 PNG 格式,一张 1920x1080 的 PNG,携带复杂图案时很容易到 2MB 以上,内容复杂一些 5MB 都不奇怪。这些图被塞进 xhEditor 的 HTML 里,问题就来了。
1.2 base64 编码让图片体积再暴增三分之一
PPT 复制到编辑器里的图片不是上传到服务器后的 URL,也不是一个临时文件,而是以 dataURL 形式嵌在 img 标签里。dataURL 会把图片文件的二进制数据转成 base64 字符串,这个过程有一个天然的体积放大系数。
base64 用 64 个可见字符去表达 8 位二进制数据,每 3 个字节会被编码成 4 个字符。也就是说编码后字符串长度约为原始字节数的 4/3,也就是大约多出 33%。一张 2.4MB 的 PNG,经过 base64 编码后,字符串长度大约是 3.2MB,再加上 MIME 头、IMG 标签、width 高度属性,最终嵌入到 HTML 里的长度会更夸张。
这些数据在提交表单时,会作为整个 HTML 字符串的一部分 POST 到后端。假设运营一篇帖子贴了 6 张 PPT 图,那么前端就要把接近 20MB 的字符串往上抛。弱网环境必然卡顿,即使内网带宽够,后端也要解析、存储一个巨大的 TEXT 字段,这对接口和数据库都是一次沉重打击。
1.3 xhEditor 的特性让这个问题被无限放大
xhEditor 和很多现代编辑器不同,它基于 textarea 加 iframe,本身并不提供粘贴图片上传、自动压缩、外部存储这些能力。你从 PPT 复制进去的大图,会被原封不动地放进 iframe 的 body 里,最终保存时 xhEditor 把整个 body.innerHTML 取出来,同步到 textarea,然后再交给 form 或 ajax 提交。
这个链路意味着没有人会主动帮你在“粘贴”和“提交”之间做任何中间处理。编辑器本身不压缩,后端如果接的是普通开发框架,也不会主动去解析并压缩富文本里的 base64。于是图片体积就一路畅通地打到数据库。很多老后台的文章表字段是 varchar 或 TEXT,遇到几个超大 base64 图,轻则存储占用上升,重则数据库连接超时。
所以,要不改编辑器内核的前提下解决问题,最合理的介入点就是前端在粘贴完成后、图片进入 iframe 后,马上做一轮自动压缩处理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体方案设计:把压缩时机放在粘贴后而不是保存前
2.1 为什么选“粘贴后立即压缩”这条链路
一开始我也想过,是否要在用户点保存时再做压缩。后来实测下来发现不行,原因有三点。
- 第一,保存时才压缩会让用户等待时间特别集中在点击“提交”的那一刻,大图一多,用户很容易以为页面卡死或者后台出错。
- 第二,保存前的统一处理一旦失败,错误定位复杂,用户根本不知道是数据问题还是后端问题。
- 第三,PPT 粘贴的图片大概率后面还会被用户反复编辑、拖拽、定位,如果不提前压缩,中间每一次操作都会带着超大 DOM 字符串,编辑器的反应会越来越迟钝。
所以压缩时机要尽可能靠前。我在 xhEditor 的 paste 事件触发后,延迟一小段时间,等浏览器完成了默认粘贴行为,再遍历编辑器内的 img 元素,把超大 dataURL 图压缩并替换 src。用户几乎感知不到这个过程,图片刚粘进来就已经是压缩后的版本。
2.2 为什么用 Canvas 而不是引入第三方压缩库
浏览器压缩图片最成熟的手段就是 Canvas。思路是先 new 一个 Image 对象加载原始 dataURL,然后用 Canvas 按目标尺寸重绘,最后调用 canvas.toDataURL 导出 JPEG。这个方案不依赖任何外部包,原生 API 能搞定。老后台项目如果还在用 script 标签引 xhEditor,没有打包构建环境,不用引 npm 包反而是最大的优点。
我也评估过 browser-image-compression、compressorjs 这些库。它们的底层依旧要借助 Canvas 或 WebAssembly,而且引入之后还要处理模块格式、版本依赖、兼容旧浏览器这些问题。对于一个维护成本极低的老项目,原生代码侵入性最小、最好调试,后续要删也好删。
需要承认的是,Canvas 重绘在图片特别大时会有一定的 CPU 和内存开销。PPT 复制出来的图片分辨率虽高,但通常不会超过 5000 像素宽,等比例缩小到 1600 宽度再导出,在平时配置的电脑上大概几十毫秒到一百毫秒,不会造成明显卡顿。
2.3 图片处理边界:哪些必须压,哪些应该跳过
不是所有粘贴进来的图片都需要压缩。工程设计里最忌讳一刀切。你要先给代码定下几条判断规则。
第一条规则:只处理 dataURL 或 blob 形式的图片,外链 http/https 图片不处理。远端图片有跨域限制,Canvas 读取会受到污染,而且外链图通常有自己的 CDN 和存储策略,不该由前台替人家压缩。
第二条规则:图片原始 dataURL 长度小于某个阈值的跳过。我平时设的阈值是 100KB。小于这个体量的图对后台保存没有压力,还保留原始清晰度,不需要压缩。否则你贴一张很小的图标、二维码,反而被二次转码搞糊了。
第三条规则:格式上优先处理 PNG、JPEG、WEBP,SVG 和 GIF 尽量跳过。GIF 动图一旦走过 Canvas 就只有第一帧了,SVG 转位图会丢失矢量清晰度,这两类格式碰都不要碰。
3. 实操实现:把图片自动压缩清楚接进 xhEditor
3.1 第一步:让 paste 事件落在编辑器的 iframe 文档上
xhEditor 初始化之后会隐藏原来的 textarea,在旁边生成一个 iframe,真正的可编辑区域是 iframe.contentDocument.body。所以不能把 paste 事件绑在 textarea 上,要绑到 iframe 内部 document。
不同版本 xhEditor 生成的 iframe id 命名并不完全一样,我这边用的版本当中,iframe 的 id 大体会是 xheditor_ 加上原 textarea 的 id。更稳妥的兜底方式是从 textarea 的相邻节点里找。下面这个函数可以根据 textarea DOM 动态定位 iframe 的 contentDocument。
javascript复制function getXhEditorDoc(textarea) {
var tid = textarea.id || textarea.name;
var iframe = null;
if (tid) {
iframe = document.getElementById('xheditor_' + tid)
|| document.getElementById(tid + '_xheditor');
}
if (!iframe) {
var el = textarea.nextElementSibling || textarea.nextSibling;
while (el) {
if (el.tagName === 'IFRAME' && el.contentDocument) {
iframe = el;
break;
}
el = el.nextElementSibling || el.nextSibling;
}
}
if (!iframe || !iframe.contentDocument) {
throw new Error('未找到 xhEditor 的 iframe,请检查初始化是否完成');
}
return iframe.contentDocument;
}
这段代码里我故意做了两层定位。第一层按 id,第二层找相邻 iframe。实际项目里 xhEditor 的皮肤、版本、初始化方式千奇百怪,你要是只写死一种 id 规则,很可能在别人的版本上直接失效。用相邻节点兜底虽然丑,但胜在稳定。
3.2 第二步:监听 paste,等待浏览器粘贴完成后遍历图片
事件绑定要讲究顺序。因为 xhEditor 内部会自己处理粘贴事件,并往 iframe 的 body 里写入 HTML,这个动作可能需要几毫秒到几十毫秒。如果你在 paste 触发时立刻去遍历 body,很可能还没看到新图片。我在绑定事件的回调里加了一个短延迟,给浏览器默认粘贴行为留出时间。
javascript复制var doc = getXhEditorDoc(document.getElementById('content'));
doc.addEventListener('paste', function () {
setTimeout(function () {
compressEditorImages(doc);
}, 50);
});
}
function compressEditorImages(doc) {
var imgs = doc.querySelectorAll('img');
imgs.forEach(function (img) {
var src = img.getAttribute('src') || '';
if (!needCompress(src)) return;
compressDataURL(src).then(function (newSrc) {
if (newSrc && newSrc !== src) {
img.setAttribute('src', newSrc);
img.removeAttribute('srcset');
}
});
});
}
在遍历之前必须有过滤函数。我下面这个函数里还额外处理了一个容易忽略的情况:如果图片 src 以 blob: 开头,说明浏览器没有把图片转成 base64,而是生成了一个临时对象地址。这种地址在页面刷新后就会失效,如果不处理,用户复制粘贴后的图片刷新页面前后看到的内容不一致。所以对于 blob 地址,只要长度够大,也要调用压缩逻辑转成 dataURL。
javascript复制function needCompress(src) {
if (!src) return false;
if (src.indexOf('data:image/') === 0) {
if (src.indexOf('data:image/svg') === 0) return false;
if (src.indexOf('data:image/gif') === 0) return false;
return src.length > 100 * 1024;
}
if (src.indexOf('blob:') === 0) return true;
return false;
}
这里先把最小体积阈值写死成 100KB,你之后可以根据自己后台的带宽和数据库情况调整。
3.3 第三步:用 Canvas 重绘并导出压缩版 dataURL
压缩逻辑最关键的函数是 loadImage 和 drawImage 两步。先把 dataURL 加载进 Image,等图片 onload 之后再计算目标宽高、绘制 Canvas、导出 JPEG。
javascript复制function loadImage(src) {
return new Promise(function (resolve, reject) {
var img = new Image();
img.onload = function () { resolve(img); };
img.onerror = function () { reject(new Error('图片加载失败')); };
img.src = src;
});
}
function compressDataURL(src) {
return loadImage(src).then(function (img) {
var maxWidth = 1600;
var maxHeight = 1600;
var quality = 0.82;
var naturalWidth = img.naturalWidth || img.width;
var naturalHeight = img.naturalHeight || img.height;
if (naturalWidth <= maxWidth && naturalHeight <= maxHeight && src.indexOf('data:image/jpeg') === 0) {
return src;
}
var scale = Math.min(
1,
maxWidth / naturalWidth,
maxHeight / naturalHeight
);
var targetWidth = Math.max(1, Math.round(naturalWidth * scale));
var targetHeight = Math.max(1, Math.round(naturalHeight * scale));
var canvas = document.createElement('canvas');
canvas.width = targetWidth;
canvas.height = targetHeight;
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, targetWidth, targetHeight);
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
var newSrc = canvas.toDataURL('image/jpeg', quality);
return newSrc.length < src.length ? newSrc : src;
}).catch(function () {
return src;
});
}
代码里有一个容易被忽视的点:先把 canvas 填充成白色,再 drawImage。因为 PNG 图片如果有透明区域,直接导出 JPEG 时,透明部分默认会变成黑色。PPT 里的页面背景经常是纯白或浅色,你从 PPT 复制出来的图如果边缘还有透明像素,不填白底就会出现一块刺眼的黑边。
质量参数 0.82 是我调过很多版本后的结果。PPT 粘贴内容通常包含大段文字和图形,质量 0.75 以下文字边缘会明显发虚,0.9 以上导出文件体积下降有限。0.82 这个值在视觉和体积之间比较均衡。如果你对清晰度要求特别高,可以提到 0.85 或 0.88,但体积会相应增加。
3.4 第四步:压缩结果回填时,千万别漏掉 srcset 清理
拼接 dataURL 图回车到 img 的 src 后,很多人的第一反应是完事。但实际测试里我发现,有些浏览器或从 Office 复制出来的 HTML 片段会给 img 同时生成 srcset 属性,里面可能还是原始大图的 dataURL 或 2x 地址。如果在替换 src 之后不管 srcset,浏览器渲染时仍然会优先用 srcset 里的高分辨率图,等于压缩了个寂寞。
所以替换 src 后必须顺手把 srcset 属性删掉,还需要把原本可能在 img 上遗留的 data-src、data-original 这类属性也清一下。
javascript复制img.setAttribute('src', newSrc);
img.removeAttribute('srcset');
img.removeAttribute('data-src');
img.removeAttribute('data-original');
这个环节是典型的不踩坑根本不知道的细节。我第一次做压缩功能时只改了 src,测试时发现最终保存的数据已经变小了,但聊天气泡里图片依旧很大,排查了半天才意识到是 srcset 在起作用。
3.5 完整脚本样板:可以直接拿去的模块化写法
下面是一段我在项目里实际跑过的精简版脚本。它把前面几个步骤整合成了一个 PasteImageCompressor 类,你只要传入 xhEditor 的 textarea 元素,start 一下就算接完。
javascript复制class XhePasteImgCompressor {
constructor(textarea, options) {
this.textarea = textarea;
this.options = Object.assign({
maxWidth: 1600,
maxHeight: 1600,
quality: 0.82,
minLength: 100 * 1024
}, options);
this.doc = null;
}
start() {
var self = this;
this.doc = this.getEditorDoc();
this.doc.addEventListener('paste', function () {
setTimeout(function () {
self.compressAll();
}, 50);
});
}
getEditorDoc() {
var textarea = this.textarea;
var tid = textarea.id || textarea.name;
var iframe = null;
if (tid) {
iframe = document.getElementById('xheditor_' + tid)
|| document.getElementById(tid + '_xheditor');
}
if (!iframe) {
var el = textarea.nextElementSibling || textarea.nextSibling;
while (el) {
if (el.tagName === 'IFRAME' && el.contentDocument) {
iframe = el;
break;
}
el = el.nextElementSibling || el.nextSibling;
}
}
return iframe.contentDocument;
}
compressAll() {
var self = this;
var imgs = this.doc.querySelectorAll('img');
imgs.forEach(function (img) {
var src = img.getAttribute('src') || '';
if (!self.needCompress(src)) return;
self.compressImage(src).then(function (newSrc) {
if (newSrc && newSrc !== src) {
img.setAttribute('src', newSrc);
img.removeAttribute('srcset');
img.removeAttribute('data-src');
img.removeAttribute('data-original');
}
}).catch(function () {
// 压缩失败就保留原图,至少不能让用户内容丢失
});
});
}
needCompress(src) {
if (!src) return false;
if (src.indexOf('data:image/') === 0) {
if (src.indexOf('data:image/svg') === 0) return false;
if (src.indexOf('data:image/gif') === 0) return false;
return src.length > this.options.minLength;
}
if (src.indexOf('blob:') === 0) return true;
return false;
}
compressImage(src) {
var self = this;
return this.loadImage(src).then(function (img) {
var naturalWidth = img.naturalWidth || img.width;
var naturalHeight = img.naturalHeight || img.height;
var maxWidth = self.options.maxWidth;
var maxHeight = self.options.maxHeight;
if (naturalWidth <= maxWidth
&& naturalHeight <= maxHeight
&& src.indexOf('data:image/jpeg') === 0) {
return src;
}
var scale = Math.min(1, maxWidth / naturalWidth, maxHeight / naturalHeight);
var targetWidth = Math.max(1, Math.round(naturalWidth * scale));
var targetHeight = Math.max(1, Math.round(naturalHeight * scale));
var canvas = document.createElement('canvas');
canvas.width = targetWidth;
canvas.height = targetHeight;
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, targetWidth, targetHeight);
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
var newSrc = canvas.toDataURL('image/jpeg', self.options.quality);
return newSrc.length < src.length ? newSrc : src;
}).catch(function () {
return src;
});
}
loadImage(src) {
return new Promise(function (resolve, reject) {
var img = new Image();
img.onload = function () {
resolve(img);
};
img.onerror = function () {
reject(new Error('image load error'));
};
img.src = src;
});
}
}
接入方式非常简单,在 xhEditor 初始化完成之后执行下面这段代码。
javascript复制var compressor = new XhePasteImgCompressor(document.getElementById('content'));
compressor.start();
如果因为某些特殊原因在初始化完成不到 100 毫秒就执行这段代码,iframe 还没就绪,你也可以再加一层轮询等待。用 setInterval 每隔 200 毫秒检查 iframe 是否存在,最多等 3 秒再绑定事件。
4. 上线前必看:常见问题与排查记录
4.1 PNG 透明背景压缩后变黑底,怎么解决
这是接入压缩以后最常见的问题。Canvas 默认在绘制 JPEG 时,透明像素不在画布上占位置,导出时会被填成黑色。PPT 页面的复制图经常自带透明边缘,甚至整张图都是透明底,一旦转成 JPEG 就会变成黑底或黑边。
我的处理方式已经在压缩函数里体现:绘制前先 fillRect 填充白色背景。这样导出的 JPEG 背景会变成纯白,和 PPT 默认的白底页面视觉上是一致的。但对于真正需要保留透明背景的场景,比如一个透明底的 PNG 素材,即使填充白底后能用,背景也会被改掉。这个时候需要你把 preserveAlpha 参数打开,转成 PNG 再输出。对应的代码是把 canvas.toDataURL 的参数改成 image/png,不要填白底。代价是 PNG 导出体积比 JPEG 大不少,压缩效果没那么明显。
4.2 超大尺寸图片在 Canvas 绘制时变成空白或报错
浏览器对 Canvas 的面积是有限制的。Chrome 在绝大多数桌面设备上可以支持到 16384x16384 像素左右,但移动设备和某些低配笔记本会更低。PPT 里如果有一张超长的纵向截图,比如网页长图加 PPT 页面拼接在一起,宽高比极端,你在压缩时把整张图塞进 Canvas,绘制过程中很容易出现空白图或直接抛异常。
对策是在绘制前做一次像素总量判断。比如当 naturalWidth 乘 naturalHeight 大于 3200 万像素时,再额外压低目标尺寸,让它在单张 canvas 能承受的范围内。也可以直接判断 naturalWidth 是否超过 8000,如果超过就先降采样到 8000 以内再画。这能避免 Canvas 一次吃进过大内存。
4.3 粘贴事件绑定不上,图片一直没被压缩
最常见的排查点是,事件绑太早了。xhEditor 初始化之后 iframe 不一定会立刻加载完成,如果你在页面 DOM ready 时就调用 compressor.start,这时候 contentDocument 可能还是 undefined。改成 window.load 之后执行,或者按前面说的轮询等待,一般都能解决。
还有一种情况是用户切换到了 xhEditor 的“源码模式”。这个模式下编辑区从 iframe 变成了一个 textarea,用户在里面粘贴的是 HTML 原文,不是图片对象。如果业务上允许用户高频使用源码模式,你可以额外在源码 textarea 上绑定 paste 监听,等用户切回可视化模式时再扫描一次。不过大多数运营后台不会走到这个分支,优先级不高。
4.4 图片被重复压缩,越压越糊
如果页面里还有其他老旧的图片处理插件,或者你自己在多个地方都挂载了 paste 监听,同一张 dataURL 图片可能会被处理两次甚至更多。每次 JPEG 重编码都会增加画质损耗,二次三次压缩后文字边缘就很模糊。
我在压缩流程里给已经处理过的 img 加了一个 data-compressed 属性,处理完就标记。
javascript复制img.setAttribute('data-compressed', 'true');
在 needCompress 函数开头先判断 img 标签上有没有这个标记,有就直接跳过。这样即使有多处监听,同一张图也只会被压一次。还要注意 from clipboard 中如果复制过来的 HTML 本来已经带了 data-compressed,要看具体场景决定是否处理,我建议不做特殊豁免,因为粘贴的二手 HTML 不可信。
4.5 压缩完之后数据还是大,提交后端报错
前端压缩能解决大部分问题,但解决不了所有问题。比如一张 4000x2000 的高清内容截图,压缩到 1600 后仍然有 250KB,base64 之后接近 340KB。如果数据库字段设计的是 varchar(500) 或很小的 text,依然会报超长。
遇到这种情况要分两层处理。第一层是在 pid 控件的提交校验里增加一个对隐藏域或 textarea 的值长度判断,超过阈值就阻止提交并提示用户。第二层是跟前端一起把编辑器的图片改成真正上传到对象存储,再把 URL 放到 img 里。前端的自动压缩只能降低体积和概率,要根治大图建议走上传链路。如果一时半会儿改不动后端,至少把压配额调狠一点,比如 maxWidth 调成 1280,quality 调成 0.72,PPT 文字内容依旧能看,但体积能再砍一半。
4.6 切到源码模式后发现 img src 还是旧地址
xhEditor 在可视化模式里直接修改 iframe body 的 DOM,textarea 并不会实时同步。你在压缩函数里改了 img 的 src,但不一定会触发编辑器的内部更新,导致用户切换到源码模式时看到的 HTML 还是粘贴后的旧数据,一旦用户用源码模式再次保存,压缩结果就被覆盖回去了。
我在实际处理时会在压缩完成之后主动触发一次编辑器的 keyup 事件,让 xhEditor 认为用户输入过内容,从而把内部状态同步一次。不同版本的 xhEditor API 有所差异,这个同步事件并不是标准公开 API。如果你在项目中发现源码模式和可视化模式内容不一致,建议使用 xhEditor 自带的“切换源码”按钮来强制刷新,或者找编辑器实例的 save 方法调用。老代码不统一,没有一劳永逸的答案,只能按你本机版本的实现去接。
5. 实测数据、参数建议和后续扩展方向
5.1 几组典型场景的压缩前后对比
我把几个常见 PPT 粘贴场景拿到压缩函数里跑了一下,配置是 maxWidth 1600、maxHeight 1600、quality 0.82,得到一组可用于参考的数据。
| 场景 | 原始格式 | 原始体积 | 压缩后体积 | 压缩比例 |
|---|---|---|---|---|
| PPT 整页图文复制,1920 宽 | PNG | 2.4MB | 约 150KB | 约 16:1 |
| 放大的高分屏幻灯片截图,2400 宽 | PNG | 4.1MB | 约 230KB | 约 18:1 |
| 纯色图文版式,1440 宽 | PNG | 800KB | 约 90KB | 约 9:1 |
| 页面里零散复制的小图,600 宽 | PNG | 60KB | 不压缩 | 原样保留 |
可以看到,真正能压下去的是本身内容复杂、色彩层次多的 PPT 页面图。纯色图标不是很大,又不到 100KB,压缩逻辑会直接跳过,这也符合我们的目标。前几行数据说明,一个 20MB 的粘贴请求,经过自动压缩后基本能降到 1.5MB 以内,保存接口再也不会因为 PPT 图卡住。
需要提醒一句:压缩效果和图片内容强相关。同样尺寸如果是满屏的高清照片,JPEG 导出后 200KB 也正常;如果是一整页只有线条和文字的 PPT,压缩到 80KB 左右也不奇怪。你拿到的实际数字会和我这里不完全相同,没必要纠结具体值,只要了解大概的量级就够了。
5.2 不同内容场景的压缩参数怎么给
不是所有后台都适合一套参数。如果你们的后台文章主要在手机端阅读,图片最大宽度 1280 就足够,质量 0.78 就能让手机上的阅读体验看不出发虚。如果后台内容要给甲方看大
