利用es2024新特性,图片压缩及上传
📅 2026/7/5 8:56:08
👁️ 阅读次数
📝 编程学习
背景:在微信内置的浏览器,上传图片时,因考虑到手机端的图片较大,一般在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 lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.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 id="file" type="file" accept="image/jpeg,image/png,image/jpg"> <br> <button id="uploadBtn">上传</button> <input id="imageUrl" type="hidden" value=""> <img id="preview" alt="预览图"> <pre id="log"></pre> <script type="module" src="__API__/js/upload-webp.js"></script> </body> </html>
编程学习
技术分享
实战经验