canvas 压缩图片
拖动 range, 查看压缩效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { padding: 20px; } .img-preview { --clip-width: 0px; display: none; /* display: grid; */ position: relative; grid-template-columns: 1fr 1fr; margin-top: 20px; gap: 10px; width: 500px; } img { position: absolute; inset: 0; width: 100%; height: auto; padding: 10px; border: 1px solid #ccc; border-radius: 6px; user-select: none; } #source-img { clip: rect(auto, var(--clip-width), auto, auto); z-index: 2; } .line { --line-h: 0px; --line-x: 0px; position: absolute; top: 0; left: 0; width: 10px; height: var(--line-h); translate: var(--line-x) 0; background-color: rgba(153, 153, 153, 0.5); z-index: 3; cursor: move; user-select: none; } </style> </head> <body> <input type="file" id="file" /> <br /><br /> <input type="range" id="quality" min="0" max="1" step="0.1" value="0.9" /> <br /> <div class="img-preview"> <img alt="" id="source-img" /> <img alt="" id="compressed-img" /> <div class="line"></div> </div> <script> const imgPreview = document.querySelector('.img-preview'); const line = imgPreview.querySelector('.line'); init(); function init() { const file = document.getElementById('file'); file.addEventListener('change', handleImageCompress); line.addEventListener('mousedown', handleLineMovedown); } function handleLineMovedown(e) { imgPreview.addEventListener('mousemove', handleImgPreviewMove); line.addEventListener('mouseup', handleLineMouseup); } function handleImgPreviewMove(e) { let x = e.clientX - imgPreview.offsetLeft - line.offsetWidth / 2; x = Math.max(x, 0); x = Math.min(x, imgPreview.offsetWidth - line.offsetWidth); line.style.setProperty('--line-x', `${x}px`); imgPreview.style.setProperty('--clip-width', `${x}px`); } function handleLineMouseup() { imgPreview.removeEventListener('mousemove', handleImgPreviewMove); line.removeEventListener('mouseup', handleLineMouseup); } async function handleImageCompress(e) { /** @type {File} */ const file = e.target.files[0]; if (!file) return; const imgPreview = document.querySelector('.img-preview'); imgPreview.style.display = 'none'; // 读取图片的base64数据 const sourceSrc = await readFileDataUrl(file); // 显示图片 const sourceImg = document.getElementById('source-img'); sourceImg.onload = () => { const compressedImg = document.getElementById('compressed-img'); const qualityRange = document.getElementById('quality'); const line = document.querySelector('.line'); qualityRange.value = 0.9; const canvas = createCanvas(sourceImg); queueMicrotask(() => { line.style.setProperty('--line-h', `${sourceImg.offsetHeight}px`); const x = (sourceImg.offsetWidth - line.offsetWidth) / 2; line.style.setProperty('--line-x', `${x}px`); imgPreview.style.setProperty('--clip-width', `${x}px`); }); qualityRange.addEventListener('change', (e) => { const quality = +e.target.value; // 压缩图片 const compressedSrc = doCompression2(canvas, sourceSrc, file.type, quality); // 显示压缩后的图片 compressedImg.src = compressedSrc; }); // 压缩图片 const compressedSrc = doCompression(canvas, sourceSrc, file.type); // 显示压缩后的图片 compressedImg.src = compressedSrc; imgPreview.style.display = 'grid'; }; sourceImg.src = sourceSrc; } /** * 创建 canvas 并绘制图片 * @param {HTMLImageElement} img */ function createCanvas(img) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); return canvas; } function doCompression2(canvas, imgSrc, type, quality = 0.9) { const compressedSrc = canvas.toDataURL(type, quality); console.log('doCompression2 => ', compressedSrc.length, imgSrc.length, type, quality); return compressedSrc; } /** * 压缩处理 * @param {HTMLCanvasElement} canvas * @param {HTMLImageElement} img * @param {String} filetype * @param {Number} quality */ function doCompression(canvas, imgSrc, type, quality = 0.9) { const compressedSrc = canvas.toDataURL(type, quality); // 判断压缩后的base64数据是否大于原图的base64数据 if (compressedSrc.length >= imgSrc.length) { if (quality <= 0) { return doCompression(canvas, imgSrc, 'image/webp'); } return doCompression(canvas, imgSrc, type, Number((quality - 0.1).toFixed(1))); } console.log('doCompression => ', compressedSrc.length, imgSrc.length, type, quality); return compressedSrc; } function readFileDataUrl(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.addEventListener('load', () => { resolve(reader.result); }); reader.readAsDataURL(file); }); } </script> </body> </html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!