尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

自制压缩小程序

自制压缩小程序 好久不更新了今天来更一下众所周知要压缩多张照片并且大小限制得比较严格用AI帮忙又容易给你搞砸了而其他网站的压缩工具要么不能严格给你压缩成你想要的大小要么容易泄露隐私。于是我搞了一上午写了个网页小程序帮忙压缩测试过效果还不错源码分享给大家复制源码保存为html文件即可使用了离线也能使用很好地避免泄密的问题(▽)喜欢的话点点赞给个关注支持一下。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title高效批量图片压缩工具V1.6.5/title style :root { --primary-color: #3498db; --error-color: #e74c3c; } body { font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; max-width: 800px; margin: 0 auto; padding: 30px; background-color: #f5f7fa; } h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; font-size: 2.2em; text-shadow: 2px 2px 4px rgba(0,0,0,0.1); } .input-group { margin: 25px 0; text-align: center; } .input-row { display: flex; align-items: center; justify-content: center; gap: 10px; margin: 15px 0; } input[typefile] { padding: 10px; background: #fff; border: 2px solid var(--primary-color); border-radius: 6px; width: 300px; cursor: pointer; transition: all 0.3s; } #fileList { margin: 15px 0; padding: 10px; border: 2px dashed var(--primary-color); border-radius: 6px; min-height: 60px; } .file-item { padding: 8px; margin: 5px; background: #e8f4ff; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; } .progress-container { margin: 20px 0; } #globalProgress { height: 10px; background-color: #eee; border-radius: 5px; overflow: hidden; } #currentProgress { height: 10px; background-color: #2ecc71; width: 0%; transition: width 0.3s ease; } .status-text { color: #666; margin: 10px 0; font-size: 0.9em; text-align: center; } button { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; transition: 0.3s; } button.primary { background-color: var(--primary-color); color: white; } button.cancel { background-color: var(--error-color); color: white; } media (max-width: 600px) { body { padding: 15px; } input[typefile] { width: 100%; } .input-row { flex-direction: column; } button { width: 100%; margin-top: 10px; } } /style script srchttps://cdn.jsdelivr.net/npm/fflate0.7.4/umd/index.min.js/script /head body h1 高效批量图片压缩工具V1.6.5/h1 div classinput-group input typefile idimageInput acceptimage/* multiple /div div idfileList/div div classinput-group div classinput-row label fortargetSize目标大小/label input typenumber idtargetSize step0.1 min0.1 value1 select idunitSelect option valueMBMB/option option valueKBKB/option /select /div /div div classprogress-container div idglobalProgress div idcurrentProgress/div /div div classstatus-text idstatus准备就绪/div /div div classinput-group button classprimary onclickstartCompression()开始批量压缩/button button classcancel onclickcancelCompression()取消操作/button /div h3 GAMES游戏出版社/h3 h3 GAMES工作室/h3 h5 lyh1190出品/h5 script // 配置常量 const CONCURRENCY 3; const MAX_QUALITY 0.9; const MIN_QUALITY 0.3; const SIZE_STEP 0.1; let files []; let cancelRequested false; let errorFiles []; // 文件选择处理 document.getElementById(imageInput).addEventListener(change, async (e) { files Array.from(e.target.files); await updateFileListWithEstimation(); }); async function updateFileListWithEstimation() { const fileList document.getElementById(fileList); const items await Promise.all(files.map(async (file, index) { const estimatedSize await estimateCompressedSize(file); return div classfile-item span ${file.name}br small原大小: ${formatSize(file.size)} → 预估: ${estimatedSize}/small /span button onclickremoveFile(${index})×/button /div ; })); fileList.innerHTML items.join(); } async function estimateCompressedSize(file) { try { const img await loadImage(file); const canvas document.createElement(canvas); const blob await drawImageToBlob(img, canvas, 0.7, 0.8); return formatSize(blob.size); } catch { return 估算失败; } } function removeFile(index) { files.splice(index, 1); updateFileListWithEstimation(); } async function startCompression() { if (!files.length) return alert(请选择图片文件); resetState(); const startTime Date.now(); const zip {}; try { const targetSizeBytes calculateTargetSize(); const chunks chunkArray(files, CONCURRENCY); for (const [chunkIndex, chunk] of chunks.entries()) { if (cancelRequested) break; const results await Promise.all(chunk.map(async (file) { try { const blob await optimizedCompression(file, targetSizeBytes); // 转换为Uint8Array const arrayBuffer await blob.arrayBuffer(); return { file, uint8Array: new Uint8Array(arrayBuffer) }; } catch (error) { errorFiles.push(file.name); return null; } })); results.forEach((result, index) { if (result) { const filename compressed_${result.file.name}; zip[filename] result.uint8Array; } updateProgress( (chunkIndex * CONCURRENCY index 1) / files.length ); }); updateStatus(chunkIndex, chunks.length, startTime); } if (!cancelRequested) { const zipped fflate.zipSync(zip); // 创建正确类型的Blob saveAs(new Blob([zipped], { type: application/zip }), compressed_images.zip); } } catch (error) { handleError(error); } finally { finalizeProcess(startTime); } } // 核心压缩逻辑二分法优化 async function optimizedCompression(file, targetSize) { const img await loadImage(file); const canvas document.createElement(canvas); let bestBlob null; // 质量二分法优化 let low MIN_QUALITY, high MAX_QUALITY; for (let i 0; i 6; i) { const mid (low high) / 2; const blob await drawImageToBlob(img, canvas, mid, 1); if (blob.size targetSize) { bestBlob blob; low mid; } else { high mid; } } // 尺寸调整作为后备方案 if (!bestBlob) { const scale 0.8; const adjustedQuality 0.7; bestBlob await drawImageToBlob(img, canvas, adjustedQuality, scale); if (bestBlob.size targetSize) { throw new Error(无法压缩到目标大小); } } return bestBlob; } // 工具函数 function loadImage(file) { return new Promise((resolve, reject) { const reader new FileReader(); reader.onload (e) { const img new Image(); img.onload () resolve(img); img.onerror reject; img.src e.target.result; }; reader.readAsDataURL(file); }); } function drawImageToBlob(img, canvas, quality, scale) { canvas.width img.width * scale; canvas.height img.height * scale; const ctx canvas.getContext(2d); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); return new Promise((resolve) { canvas.toBlob( (blob) resolve(blob), image/jpeg, quality ); }); } // 其他辅助函数 function calculateTargetSize() { const size parseFloat(document.getElementById(targetSize).value); const unit document.getElementById(unitSelect).value; return size * (unit MB ? 1048576 : 1024); } function chunkArray(arr, size) { return Array.from( { length: Math.ceil(arr.length / size) }, (_, i) arr.slice(i * size, (i 1) * size) ); } function updateProgress(percentage) { document.getElementById(currentProgress).style.width ${percentage * 100}%; } function updateStatus(chunkIndex, totalChunks, startTime) { const elapsed Math.round((Date.now() - startTime) / 1000); document.getElementById(status).textContent 正在处理 ${chunkIndex 1}/${totalChunks} 批次已用时 ${elapsed} 秒; } function resetState() { cancelRequested false; errorFiles []; document.getElementById(currentProgress).style.width 0%; document.getElementById(status).textContent 初始化中...; } function finalizeProcess(startTime) { const elapsed Math.round((Date.now() - startTime) / 1000); let status cancelRequested ? 操作已取消 : 处理完成耗时 ${elapsed} 秒; if (errorFiles.length) { status ${errorFiles.length} 个文件失败; alert(压缩失败文件\n${errorFiles.join(\n)}); } document.getElementById(status).textContent status; } function formatSize(bytes) { return bytes 1048576 ? ${(bytes / 1048576).toFixed(1)} MB : ${(bytes / 1024).toFixed(1)} KB; } function cancelCompression() { cancelRequested true; document.getElementById(status).textContent 正在取消...; } function saveAs(blob, filename) { const link document.createElement(a); link.style.display none; link.download filename; link.href URL.createObjectURL(blob); document.body.appendChild(link); link.click(); setTimeout(() { document.body.removeChild(link); URL.revokeObjectURL(link.href); }, 100); } // 错误处理函数 function handleError(error) { console.error(处理过程中发生错误:, error); alert(发生错误: ${error.message || error}); } /script /body /html本程序为我的团队“GAMES工作室”出品喜欢的话请记得点赞哦
返回列表