当前位置: 首页 > news >正文

利用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 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>
http://www.jsqmd.com/news/1127187/

相关文章:

  • Windows平台Nmap从入门到实战:网络扫描与安全审计指南
  • 05_子代理
  • 2026图片去除背景工具全解:免费在线、电脑软件、手机,APP,实操指南
  • 2026年干细胞机构观察:四家企业技术布局与服务边界梳理
  • 01_CLAUDE.md
  • 2026年CSDN年度技术趋势预测:AI、云原生与开发者工具的未来
  • 2026去水印不破坏原图的方法:电脑手机在线无痕去水印工具教程
  • Navicat密码找回:3分钟解密本地加密连接配置
  • OpenCV 4.8 图像处理实战:用代码复现3种经典视觉错觉(附对比图)
  • 股市学习心得-股市投资的理解
  • YOLO部署血泪史:PyTorch转ONNX/TensorRT/NCNN,我踩过的20个坑全在这了
  • 2026视频转文字提取全操作指南:免费工具、在线网站、手机电脑端完整教程
  • 2026图片去水印方法:手机电脑免费工具与在线网站、PS教程
  • DDR4 硬件设计实战:基于MT41J256M8的PCB布局与信号完整性分析
  • 易信easyMarkets观察:服务响应、风控提示和使用秩序的综合参考
  • 2026年最新好用英语单词软件推荐 帮你稳步提升日常英语水平
  • Agent 上了岗,然后呢?四个被忽视的问题与一种构建思路
  • 我们在焦虑什么
  • 第二章:从零到一,构建经典电机控制逻辑
  • AI智能体开发实战:基于Coze与Dify平台的快速构建与部署指南
  • 百考通AI输入题目输出高质量开题初稿
  • 5分钟上手perlporter:从安装到生成第一个Perl RPM包的完整指南
  • [Android] Namida-高颜值音乐播放-不输椒盐
  • XUnity.AutoTranslator:5分钟搞定Unity游戏多语言翻译的终极方案
  • 查重没问题,AI爆红?百考通AI专治误判
  • 体验过市场口碑好的鱼缸工厂,实际效果究竟怎么样?
  • 【关于分布式事务一致性】
  • CARLA 0.9.16 与 ROS 2 Foxy 桥接:3个关键步骤实现自动驾驶算法闭环测试
  • 告别内卷式养生,女性的温柔轻养之道
  • 【AI大模型进阶】参数入门:temperature等核心参数作用详解