PageSourceSearch

https://fangg.netlify.app/js/apps/imageCompressor.js

js fangg.netlify.app collected 2026-10-03 10:43:36 UTC 5,269 bytes, 165 lines download raw bytes

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.