1// imageCompressor.js - å端å¾çæ¹éå缩/转档/ç¼©æ¾ 2(function () { 3 const fileInput = document.getElementById('compressor-file'); 4 const dropZone = document.getElementById('compressor-drop'); 5 const formatSel = document.getElementById('compressor-format'); 6 const qualityRange = document.getElementById('compressor-quality'); 7 const qualityLabel = document.getElementById('compressor-quality-label'); 8 const maxWidthInput = document.getElementById('compressor-maxwidth'); 9 const runBtn = document.getElementById('compressor-run'); 10 const downloadBtn = document.getElementById('compressor-download'); 11 const statsEl = document.getElementById('compressor-stats'); 12 const originalImg = document.getElementById('compressor-original'); 13 const outputImg = document.getElementById('compressor-output'); 14 if (!fileInput || !dropZone) return; 15 16 let selectedFile = null; // File 17 18 function formatBytes(bytes) { 19 if (bytes === 0) return '0 B'; 20 const k = 1024; 21 const sizes = ['B', 'KB', 'MB', 'GB']; 22 const i = Math.floor(Math.log(bytes) / Math.log(k)); 23 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; 24 } 25 26 function updateQualityLabel() { 27 qualityLabel.textContent = qualityRange.value + '%'; 28 } 29 30 // ç§»é¤è¿åº¦ç¸å ³å½æ° 31 32 async function loadFile(file) { 33 if (!file || !file.type.startsWith('image/')) return null; 34 return new Promise((resolve) => { 35 const reader = new FileReader(); 36 reader.onload = (e) => { 37 const img = new Image(); 38 img.onload = () => { 39 resolve(img); 40 }; 41 img.onerror = () => resolve(null); 42 img.src = e.target.result; 43 }; 44 reader.onerror = () => resolve(null); 45 reader.readAsDataURL(file); 46 }); 47 } 48 49 function updateFileInfo() { 50 if (!selectedFile) { 51 statsEl.textContent = 'æªå è½½å¾ç'; 52 downloadBtn.style.display = 'none'; 53 originalImg.src = ''; 54 outputImg.src = ''; 55 return; 56 } 57 statsEl.textContent = `已鿩å¾çï¼å¤§å°: ${formatBytes(selectedFile.size)}`; 58 } 59 60 function handleFiles(files) { 61 if (files && files.length > 0) { 62 selectedFile = Array.from(files).find(f => f.type.startsWith('image/')); 63 updateFileInfo(); 64 } 65 } 66 67 fileInput.addEventListener('change', () => { 68 if (fileInput.files && fileInput.files.length > 0) { 69 handleFiles(fileInput.files); 70 } 71 }); 72 73 // ææ½ 74 ;['dragenter','dragover'].forEach(ev => dropZone.addEventListener(ev, (e) => { 75 e.preventDefault(); 76 e.stopPropagation(); 77 dropZone.style.background = '#f0f0f0'; 78 })); 79 ;['dragleave','drop'].forEach(ev => dropZone.addEventListener(ev, (e) => { 80 e.preventDefault(); 81 e.stopPropagation(); 82 dropZone.style.background = ''; 83 })); 84 dropZone.addEventListener('drop', (e) => { 85 if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { 86 handleFiles(e.dataTransfer.files); 87 } 88 }); 89 90 qualityRange.addEventListener('input', updateQualityLabel); 91 updateQualityLabel(); 92 93 async function compressImage(img, file) { 94 const targetType = formatSel.value; 95 const quality = Math.max(0.1, Math.min(1, Number(qualityRange.value) / 100)); 96 const maxWidth = Math.max(50, Number(maxWidthInput.value) || 1600); 97 98 const { width, height } = img; 99 const scale = Math.min(1, maxWidth / width); 100 const dstW = Math.round(width * scale); 101 const dstH = Math.round(height * scale); 102 103 const canvas = document.createElement('canvas'); 104 canvas.width = dstW; 105 canvas.height = dstH; 106 const ctx = canvas.getContext('2d'); 107 ctx.imageSmoothingEnabled = true; 108 ctx.imageSmoothingQuality = 'high'; 109 ctx.drawImage(img, 0, 0, dstW, dstH); 110 111 const blob = await new Promise((resolve) => canvas.toBlob(resolve, targetType, quality)); 112 return blob; 113 } 114 115 async function compress() { 116 if (!selectedFile) { 117 alert('请å éæ©å¾ç'); 118 return; 119 } 120 121 runBtn.disabled = true; 122 const totalSize = selectedFile.size; 123 124 try { 125 const img = await loadFile(selectedFile); 126 if (!img) { 127 throw new Error('å è½½å¾ç失败'); 128 } 129 130 const blob = await compressImage(img, selectedFile); 131 if (!blob) { 132 throw new Error('å缩å¾ç失败'); 133 } 134 135 const ratio = ((blob.size / totalSize) * 100).toFixed(1); 136 statsEl.textContent = `宿å缩ï¼å¤§å°: ${formatBytes(totalSize)} â ${formatBytes(blob.size)} (${ratio}%)`; 137 138 // é¢è§å¾ç 139 originalImg.src = URL.createObjectURL(selectedFile); 140 outputImg.src = URL.createObjectURL(blob); 141 142 // 设置ä¸è½½é¾æ¥ 143 const url = URL.createObjectURL(blob); 144 downloadBtn.style.display = ''; 145 downloadBtn.href = url; 146 downloadBtn.download = `${selectedFile.name.replace(/\.[^.]+$/, '')}_compressed.${mimeToExt(formatSel.value)}`; 147 } catch (err) { 148 console.error('å缩è¿ç¨åºé:', err); 149 alert('å缩è¿ç¨ä¸åºç°é误'); 150 } finally { 151 runBtn.disabled = false;
152 } 153 } 154 155 function mimeToExt(mime) { 156 if (mime === 'image/jpeg') return 'jpg'; 157 if (mime === 'image/png') return 'png'; 158 if (mime === 'image/webp') return 'webp'; 159 return 'img'; 160 } 161 162 runBtn.addEventListener('click', compress); 163})(); 164 165
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.