利用es2024新特性,图片压缩及上传 背景在微信内置的浏览器上传图片时因考虑到手机端的图片较大一般在5M以上甚至更大达到10M以上手机端的浏览器对es最新语法的支持、网速、cpu处理速度及内存大小等限制我们需要对图片进行大小判断、图片在保证品质的前提等比例压缩大小然后进行上传大致分为三步1、在h5移动端利用h5新语法的特性来写2、利用es新特性同时要兼容微信内置浏览器的内核无论基于谷歌chrome或苹果的safiry,要兼容低版本的浏览器3、后端无论是python\php\java\net等接收图片并保存到服务器const MAX_SIZE 5 * 1024 * 1024; const MAX_WIDTH 2560; const MAX_HEIGHT 2560; const fileInput document.querySelector(#file); const uploadBtn document.querySelector(#uploadBtn); const preview document.querySelector(#preview); const logEl document.querySelector(#log); const imageUrlInput document.querySelector(#imageUrl); let finalBlob null; let finalFileName ; const log (...args) { logEl.textContent args.join( ) \n; }; const formatSize bytes { if (bytes 1024) return ${bytes} B; if (bytes 1024 * 1024) return ${(bytes / 1024).toFixed(2)} KB; return ${(bytes / 1024 / 1024).toFixed(2)} MB; }; const isJpgOrPng file { return [image/jpeg, image/png].includes(file.type); }; const supportWebp async () { const canvas document.createElement(canvas); canvas.width 1; canvas.height 1; return new Promise(resolve { canvas.toBlob(blob { resolve(blob?.type image/webp); }, image/webp, 0.8); }); }; const loadImage file { return new Promise((resolve, reject) { const url URL.createObjectURL(file); const img new Image(); img.onload () { URL.revokeObjectURL(url); resolve(img); }; img.onerror () { URL.revokeObjectURL(url); reject(new Error(图片读取失败)); }; img.src url; }); }; const calcTargetSize (width, height) { let targetWidth width; let targetHeight height; if (targetWidth MAX_WIDTH || targetHeight MAX_HEIGHT) { const ratio Math.min(MAX_WIDTH / targetWidth, MAX_HEIGHT / targetHeight); targetWidth Math.round(targetWidth * ratio); targetHeight Math.round(targetHeight * ratio); } return { width: targetWidth, height: targetHeight, }; }; const canvasToBlob (canvas, type image/webp, quality 0.82) { return new Promise((resolve, reject) { canvas.toBlob(blob { if (!blob) { reject(new Error(图片压缩失败)); return; } resolve(blob); }, type, quality); }); }; const imageToCanvas img { const { width, height } calcTargetSize(img.naturalWidth, img.naturalHeight); const canvas document.createElement(canvas); canvas.width width; canvas.height height; const ctx canvas.getContext(2d, { alpha: false, desynchronized: true, }); ctx.fillStyle #fff; ctx.fillRect(0, 0, width, height); ctx.drawImage(img, 0, 0, width, height); return canvas; }; const compressToWebpUnder5M async file { if (!isJpgOrPng(file)) { throw new Error(只允许上传 JPG 或 PNG 图片); } const webpOk await supportWebp(); if (!webpOk) { throw new Error(当前浏览器不支持 WebP 转换请升级微信或系统浏览器); } const img await loadImage(file); let canvas imageToCanvas(img); let quality 0.86; let blob await canvasToBlob(canvas, image/webp, quality); log(原图大小, formatSize(file.size)); log(首次 WebP, formatSize(blob.size)); if (blob.size MAX_SIZE) { return blob; } /** * 先降低质量 */ let minQuality 0.45; let maxQuality 0.86; for (let i 0; i 8; i) { quality (minQuality maxQuality) / 2; const tempBlob await canvasToBlob(canvas, image/webp, quality); if (tempBlob.size MAX_SIZE) { maxQuality quality; } else { blob tempBlob; minQuality quality; } } if (blob.size MAX_SIZE) { log(压缩后大小, formatSize(blob.size)); return blob; } /** * 如果降质量还超过 5MB再逐步缩小分辨率 */ let scale 0.9; while (blob.size MAX_SIZE scale 0.4) { const oldCanvas canvas; const newCanvas document.createElement(canvas); newCanvas.width Math.round(oldCanvas.width * scale); newCanvas.height Math.round(oldCanvas.height * scale); const ctx newCanvas.getContext(2d, { alpha: false, desynchronized: true, }); ctx.fillStyle #fff; ctx.fillRect(0, 0, newCanvas.width, newCanvas.height); ctx.drawImage(oldCanvas, 0, 0, newCanvas.width, newCanvas.height); canvas newCanvas; blob await canvasToBlob(canvas, image/webp, 0.72); log(缩放 ${Math.round(scale * 100)}% 后, formatSize(blob.size)); scale - 0.1; } if (blob.size MAX_SIZE) { throw new Error(图片过大压缩后仍超过 5MB请换一张图片); } log(最终大小, formatSize(blob.size)); return blob; }; fileInput.addEventListener(change, async event { logEl.textContent ; finalBlob null; finalFileName ; const [file] event.target.files; if (!file) { return; } try { log(正在处理图片...); const blob await compressToWebpUnder5M(file); finalBlob blob; finalFileName ${crypto.randomUUID()}.webp; const previewUrl URL.createObjectURL(blob); preview.src previewUrl; preview.style.display block; log(图片已转换为 WebP); log(待上传文件名, finalFileName); log(待上传大小, formatSize(blob.size)); } catch (error) { log(错误, error.message); fileInput.value ; } }); uploadBtn.addEventListener(click, async () { if (!finalBlob) { log(请先选择图片); return; } if (finalBlob.size MAX_SIZE) { log(图片超过 5MB禁止上传); return; } const formData new FormData(); formData.append(image, finalBlob, finalFileName); uploadBtn.disabled true; uploadBtn.textContent 上传中...; try { const response await fetch(/api/index/upload/, { method: POST, body: formData, credentials: same-origin, }); const result await response.json(); if (!response.ok || result.code ! 0) { throw new Error(result.msg || 上传失败); } imageUrlInput.value result.data.url; log(上传成功, result.data.url); } catch (error) { log(上传失败, error.message); } finally { uploadBtn.disabled false; uploadBtn.textContent 上传; } });!doctype html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title图片上传 WebP 压缩/title style body { font-family: Arial, sans-serif; padding: 16px; } #preview { max-width: 100%; margin-top: 16px; display: none; border-radius: 8px; } #log { margin-top: 16px; white-space: pre-wrap; background: #f6f6f6; padding: 12px; border-radius: 6px; font-size: 14px; } button { margin-top: 12px; padding: 10px 16px; border: 0; border-radius: 6px; background: #07c160; color: #fff; font-size: 16px; } /style /head body h3图片上传/h3 input idfile typefile acceptimage/jpeg,image/png,image/jpg br button iduploadBtn上传/button input idimageUrl typehidden value img idpreview alt预览图 pre idlog/pre script typemodule src__API__/js/upload-webp.js/script /body /html

相关新闻

最新新闻

第28篇-Live-Canvas

第28篇-Live-Canvas

【OpenClaw 从入门到精通】第 28 篇:Live Canvas — Agent 驱动的可视化工作空间 本系列定位:零基础入门,从安装配置到高级架构全覆盖。 本篇你将学到 Canvas 是什么Canvas 与 A2UI 概念创建第一个 CanvasCanvas 的展示平台 一、Canvas 概念…

2026/8/29 7:16:20
第27篇-Voice-Wake与Talk-Mode

第27篇-Voice-Wake与Talk-Mode

【OpenClaw 从入门到精通】第 27 篇:Voice Wake 与 Talk Mode 本系列定位:零基础入门,从安装配置到高级架构全覆盖。 本篇你将学到 macOS/iOS Voice Wake 唤醒词配置Android Talk Mode 连续对话TTS / STT 配置 一、Voice Wake 1.1 概念 Vo…

2026/8/29 7:16:20
第26篇-Windows-Hub

第26篇-Windows-Hub

【OpenClaw 从入门到精通】第 26 篇:Windows Hub 本系列定位:零基础入门,从安装配置到高级架构全覆盖。 本篇你将学到 Windows Hub 伴侣应用功能Node Mode / Local MCP ModeWindows 特定配置 一、Windows Hub 功能 Windows Hub 是 Windows …

2026/8/29 7:16:20
校招在线笔试真题复盘:从字符串到动态规划与数据结构优化

校招在线笔试真题复盘:从字符串到动态规划与数据结构优化

又到了一年一度的校招季,后台不少学弟学妹在问“技术类在线笔试到底考什么”“怎么准备才能不挂”。这让我想起自己当年参加小红书2019年校园招聘技术类在线笔试第一批的经历,那次笔试给我留下的印象挺深——题目难度分布合理、节奏紧凑、平台操作也比较…

2026/8/29 7:16:20
React Native生态实战:白屏优化、图表选型与国内适配指南

React Native生态实战:白屏优化、图表选型与国内适配指南

聊一聊“React Native生态”这个话题。说实话,“生态”这个词已经被各种技术文章用烂了,但RN的生态确实值得认真掰扯一下。别的不说,光看国内开发者日常讨论的几个高频问题——启动白屏怎么治、统计图用什么库、Android Studio里配环境怎么老…

2026/8/29 7:16:20
Traefik 反向代理 云原生实战:部署架构与网络验收

Traefik 反向代理 云原生实战:部署架构与网络验收

Traefik 反向代理 云原生实战:部署架构与网络验收工具地址:https://www.speedce.com 社区论坛:https://bbs.speedce.com 联系:speedceadsgmail.com写在前面 围绕「Traefik 反向代理」,本文提供可落地的技术指南&#x…

2026/8/29 7:11:20