利用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

相关新闻

最新新闻

解锁Codex全部潜力:10个必装Skills实战指南,从通用助手到超级副驾

解锁Codex全部潜力:10个必装Skills实战指南,从通用助手到超级副驾

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 如果你刚接触 Codex,可能会觉得它已经很强大了——能写代码、能调试、能重构,甚至能帮你分析复杂的技术问题。…

2026/7/5 9:47:56
Headless Recorder:从录制到生产级Playwright/Puppeteer脚本的实战指南

Headless Recorder:从录制到生产级Playwright/Puppeteer脚本的实战指南

1. 项目概述:当“录制回放”遇上现代浏览器自动化如果你做过Web自动化测试,或者尝试过用代码控制浏览器进行数据抓取、表单提交等操作,大概率听说过Playwright和Puppeteer。这两个由微软和谷歌出品的现代浏览器自动化框架,以其强大…

2026/7/5 9:47:56
pytest-dependency依赖管理实战:解决作用域、并行执行与动态依赖难题

pytest-dependency依赖管理实战:解决作用域、并行执行与动态依赖难题

1. 项目概述与核心价值 在自动化测试的世界里,测试用例之间的依赖关系一直是个让人又爱又恨的话题。爱它,是因为它能模拟真实的业务流程,让测试更贴近实际;恨它,是因为它常常让测试套件变得脆弱不堪——一个前置用例失…

2026/7/5 9:47:56
uiautomator2图像识别性能优化:从原理到实战的300%提速指南

uiautomator2图像识别性能优化:从原理到实战的300%提速指南

1. 项目概述:为什么我们需要对uiautomator2的图像识别“动刀子”? 如果你正在用Python写Android自动化测试脚本,并且已经用上了uiautomator2这个库,那你大概率已经体验过它的便利性:基于ADB,封装了丰富的AP…

2026/7/5 9:47:56
Ubuntu原生安装Claude code

Ubuntu原生安装Claude code

一、无需 Node.js,会自动在后台更新: curl -fsSL https://claude.ai/install.sh | bash二、添加到系统环境变量 echo export PATH"$HOME/.local/bin:$PATH" >> ~/.bashrc source ~/.bashrc三、验证 claude --version claude doctor

2026/7/5 9:47:56
7Z文件加密实战指南:从AES-256原理到高安全设置

7Z文件加密实战指南:从AES-256原理到高安全设置

1. 项目概述:为什么你的文件需要7Z加密?在数字生活里,我们每天都在和文件打交道。从工作文档、私人照片到项目源码,总有一些内容你希望“锁”起来,只给自己或特定的人看。你可能听说过给压缩包设密码,但具体…

2026/7/5 9:42:56

月新闻