Download Views/Tools/ImageCompressor.cshtml from automindy/Toolhub: direct link, hf CLI and curl.
- Browser
- Download file 17.7 kB
-
https://huggingface.co/spaces/automindy/Toolhub/resolve/main/Views/Tools/ImageCompressor.cshtml
- Command line
-
hf download hf://spaces/automindy/Toolhub/Views/Tools/ImageCompressor.cshtml
-
curl -L -o ImageCompressor.cshtml https://huggingface.co/spaces/automindy/Toolhub/resolve/main/Views/Tools/ImageCompressor.cshtml
17.7 kB
| @{ | |
| ViewData["Title"] = "图片压缩工具"; | |
| Layout = "_Layout"; | |
| } | |
| <div class="container" style="padding-top: 6rem; padding-bottom: 4rem;"> | |
| <!-- 页面头部 --> | |
| <div class="text-center mb-8"> | |
| <div class="brand-icon" style="width: 4rem; height: 4rem; margin: 0 auto 1.5rem; font-size: 1.8rem;"> | |
| <i class="fas fa-compress-alt"></i> | |
| </div> | |
| <h1 class="hero-title">图片压缩工具</h1> | |
| <p class="hero-description">快速压缩图片文件,支持JPG、PNG、WebP等格式<br> | |
| 保持高质量的同时大幅减小文件体积,提升网站加载速度</p> | |
| </div> | |
| <!-- 工具主体 --> | |
| <div class="card" style="max-width: 800px; margin: 0 auto;"> | |
| <div class="card-body" style="padding: 2rem;"> | |
| <!-- 上传区域 --> | |
| <div id="uploadArea" class="upload-area"> | |
| <div class="upload-content"> | |
| <i class="fas fa-cloud-upload-alt" style="font-size: 3rem; color: var(--primary); margin-bottom: 1rem;"></i> | |
| <h3>拖拽图片到此处或点击选择</h3> | |
| <p style="color: var(--dark-2); margin-bottom: 1.5rem;">支持 JPG、PNG、WebP、GIF 格式,单文件最大 10MB</p> | |
| <button type="button" class="btn btn-primary" onclick="selectFiles()"> | |
| <i class="fas fa-upload"></i> | |
| 选择图片 | |
| </button> | |
| </div> | |
| <input type="file" id="fileInput" multiple accept="image/*" style="display: none;" onchange="handleFiles(this.files)"> | |
| </div> | |
| <!-- 压缩设置 --> | |
| <div id="settingsPanel" class="settings-panel" style="display: none;"> | |
| <h4 style="margin-bottom: 1rem;">压缩设置</h4> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;"> | |
| <div> | |
| <label>压缩质量</label> | |
| <div style="display: flex; align-items: center; gap: 1rem;"> | |
| <input type="range" id="qualitySlider" min="0.1" max="1" step="0.1" value="0.8" | |
| style="flex: 1;" oninput="updateQualityDisplay(this.value)"> | |
| <span id="qualityDisplay" style="font-weight: 600; min-width: 50px;">80%</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label>输出格式</label> | |
| <select id="formatSelect" style="width: 100%; padding: 0.75rem; border: 1px solid var(--light-2); border-radius: var(--border-radius);"> | |
| <option value="original">保持原格式</option> | |
| <option value="jpeg">JPEG</option> | |
| <option value="png">PNG</option> | |
| <option value="webp">WebP</option> | |
| </select> | |
| </div> | |
| </div> | |
| <button type="button" class="btn btn-primary" onclick="compressAllImages()" id="compressBtn"> | |
| <i class="fas fa-compress"></i> | |
| 开始压缩 | |
| </button> | |
| </div> | |
| <!-- 文件列表 --> | |
| <div id="fileList" class="file-list" style="display: none;"> | |
| <h4 style="margin-bottom: 1rem;">图片列表</h4> | |
| <div id="imageItems"></div> | |
| </div> | |
| <!-- 压缩结果 --> | |
| <div id="resultsPanel" class="results-panel" style="display: none;"> | |
| <h4 style="margin-bottom: 1rem;">压缩完成</h4> | |
| <div id="compressResults"></div> | |
| <div style="margin-top: 1.5rem; text-align: center;"> | |
| <button type="button" class="btn btn-primary" onclick="downloadAll()"> | |
| <i class="fas fa-download"></i> | |
| 下载全部 | |
| </button> | |
| <button type="button" class="btn btn-outline" onclick="resetTool()" style="margin-left: 1rem;"> | |
| <i class="fas fa-redo"></i> | |
| 重新开始 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 使用说明 --> | |
| <div class="card mt-8"> | |
| <div class="card-body"> | |
| <h3 style="margin-bottom: 1.5rem; text-align: center;">使用说明</h3> | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem;"> | |
| <div style="text-align: center;"> | |
| <div style="width: 3rem; height: 3rem; background: linear-gradient(135deg, var(--primary), var(--secondary)); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem; color: white; font-size: 1.2rem;">1</div> | |
| <h4>选择图片</h4> | |
| <p style="color: var(--dark-2); font-size: 0.875rem;">支持拖拽上传或点击选择,可同时处理多张图片</p> | |
| </div> | |
| <div style="text-align: center;"> | |
| <div style="width: 3rem; height: 3rem; background: linear-gradient(135deg, var(--primary), var(--secondary)); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem; color: white; font-size: 1.2rem;">2</div> | |
| <h4>调整设置</h4> | |
| <p style="color: var(--dark-2); font-size: 0.875rem;">设置压缩质量和输出格式,质量越低文件越小</p> | |
| </div> | |
| <div style="text-align: center;"> | |
| <div style="width: 3rem; height: 3rem; background: linear-gradient(135deg, var(--primary), var(--secondary)); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem; color: white; font-size: 1.2rem;">3</div> | |
| <h4>下载结果</h4> | |
| <p style="color: var(--dark-2); font-size: 0.875rem;">压缩完成后可单独下载或批量下载所有图片</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <style> | |
| .upload-area { | |
| border: 2px dashed var(--light-2); | |
| border-radius: var(--border-radius-lg); | |
| padding: 3rem 2rem; | |
| text-align: center; | |
| background: var(--light-1); | |
| transition: var(--transition); | |
| cursor: pointer; | |
| } | |
| .upload-area:hover { | |
| border-color: var(--primary); | |
| background: rgba(22, 93, 255, 0.05); | |
| } | |
| .upload-area.dragover { | |
| border-color: var(--primary); | |
| background: rgba(22, 93, 255, 0.1); | |
| } | |
| .settings-panel, .file-list, .results-panel { | |
| margin-top: 2rem; | |
| padding-top: 2rem; | |
| border-top: 1px solid var(--light-2); | |
| } | |
| .image-item { | |
| display: flex; | |
| align-items: center; | |
| padding: 1rem; | |
| border: 1px solid var(--light-2); | |
| border-radius: var(--border-radius); | |
| margin-bottom: 1rem; | |
| background: white; | |
| } | |
| .image-preview { | |
| width: 60px; | |
| height: 60px; | |
| object-fit: cover; | |
| border-radius: var(--border-radius); | |
| margin-right: 1rem; | |
| } | |
| .image-info { | |
| flex: 1; | |
| } | |
| .image-actions { | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 6px; | |
| background: var(--light-2); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| margin-top: 0.5rem; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| transition: width 0.3s ease; | |
| width: 0%; | |
| } | |
| .compression-stats { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 1rem; | |
| text-align: center; | |
| padding: 1rem; | |
| background: var(--light-1); | |
| border-radius: var(--border-radius); | |
| margin-bottom: 1rem; | |
| } | |
| .stat-item h5 { | |
| margin: 0 0 0.25rem; | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| color: var(--primary); | |
| } | |
| .stat-item p { | |
| margin: 0; | |
| font-size: 0.875rem; | |
| color: var(--dark-2); | |
| } | |
| /* 响应式调整 */ | |
| @@media (max-width: 768px) { | |
| .upload-area { | |
| padding: 2rem 1rem; | |
| } | |
| .compression-stats { | |
| grid-template-columns: 1fr; | |
| gap: 0.5rem; | |
| } | |
| } | |
| </style> | |
| <script> | |
| let selectedFiles = []; | |
| let compressedFiles = []; | |
| // 文件选择 | |
| function selectFiles() { | |
| document.getElementById('fileInput').click(); | |
| } | |
| // 处理文件选择 | |
| function handleFiles(files) { | |
| if (files.length === 0) return; | |
| selectedFiles = Array.from(files).filter(file => { | |
| return file.type.startsWith('image/') && file.size <= 10 * 1024 * 1024; // 10MB限制 | |
| }); | |
| if (selectedFiles.length === 0) { | |
| toolHub.showToast('请选择有效的图片文件(小于10MB)', 'error'); | |
| return; | |
| } | |
| displaySelectedFiles(); | |
| document.getElementById('settingsPanel').style.display = 'block'; | |
| document.getElementById('fileList').style.display = 'block'; | |
| } | |
| // 显示选中的文件 | |
| function displaySelectedFiles() { | |
| const container = document.getElementById('imageItems'); | |
| container.innerHTML = ''; | |
| selectedFiles.forEach((file, index) => { | |
| const item = document.createElement('div'); | |
| item.className = 'image-item'; | |
| item.innerHTML = ` | |
| <img class="image-preview" src="${URL.createObjectURL(file)}" alt="${file.name}"> | |
| <div class="image-info"> | |
| {file.name} | |
| {formatFileSize(file.size)} • ${file.type} | |
| <div class="progress-bar" id="progress-${index}" style="display: none;"> | |
| <div class="image-actions"> | |
| <button class="btn btn-outline btn-sm" onclick="removeFile(${index})"> | |
| `; | |
| container.appendChild(item); | |
| }); | |
| } | |
| // 移除文件 | |
| function removeFile(index) { | |
| selectedFiles.splice(index, 1); | |
| if (selectedFiles.length === 0) { | |
| document.getElementById('settingsPanel').style.display = 'none'; | |
| document.getElementById('fileList').style.display = 'none'; | |
| } else { | |
| displaySelectedFiles(); | |
| } | |
| } | |
| // 更新质量显示 | |
| function updateQualityDisplay(value) { | |
| document.getElementById('qualityDisplay').textContent = Math.round(value * 100) + '%'; | |
| } | |
| // 压缩所有图片 | |
| async function compressAllImages() { | |
| const quality = parseFloat(document.getElementById('qualitySlider').value); | |
| const format = document.getElementById('formatSelect').value; | |
| compressedFiles = []; | |
| document.getElementById('compressBtn').disabled = true; | |
| document.getElementById('compressBtn').innerHTML = '<i class="fas fa-spinner fa-spin"></i> 压缩中...'; | |
| for (let i = 0; i < selectedFiles.length; i++) { | |
| const file = selectedFiles[i]; | |
| document.getElementById(`progress-${i}`).style.display = 'block'; | |
| try { | |
| const compressedFile = await compressImage(file, quality, format, (progress) => { | |
| document.getElementById(`progress-fill-${i}`).style.width = progress + '%'; | |
| }); | |
| compressedFiles.push(compressedFile); | |
| } catch (error) { | |
| console.error('压缩失败:', error); | |
| toolHub.showToast(`压缩 ${file.name} 失败`, 'error'); | |
| } | |
| } | |
| showResults(); | |
| document.getElementById('compressBtn').disabled = false; | |
| document.getElementById('compressBtn').innerHTML = ' 开始压缩'; | |
| } | |
| // 压缩单个图片 | |
| function compressImage(file, quality, format, onProgress) { | |
| return new Promise((resolve, reject) => { | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| const outputFormat = format === 'original' ? file.type : `image/${format}`; | |
| canvas.toBlob((blob) => { | |
| if (blob) { | |
| const compressedFile = new File([blob], | |
| getCompressedFileName(file.name, format), | |
| { type: outputFormat } | |
| ); | |
| onProgress(100); | |
| resolve({ | |
| original: file, | |
| compressed: compressedFile, | |
| compressionRatio: ((file.size - blob.size) / file.size * 100).toFixed(1) | |
| }); | |
| } else { | |
| reject(new Error('压缩失败')); | |
| } | |
| }, outputFormat, quality); | |
| }; | |
| img.onerror = () => reject(new Error('图片加载失败')); | |
| img.src = URL.createObjectURL(file); | |
| // 模拟进度 | |
| let progress = 0; | |
| const progressInterval = setInterval(() => { | |
| progress += 10; | |
| if (progress >= 90) { | |
| clearInterval(progressInterval); | |
| } else { | |
| onProgress(progress); | |
| } | |
| }, 50); | |
| }); | |
| } | |
| // 获取压缩后的文件名 | |
| function getCompressedFileName(originalName, format) { | |
| if (format === 'original') return originalName; | |
| const nameWithoutExt = originalName.substring(0, originalName.lastIndexOf('.')); | |
| return `${nameWithoutExt}_compressed.${format === 'jpeg' ? 'jpg' : format}`; | |
| } | |
| // 显示压缩结果 | |
| function showResults() { | |
| const totalOriginalSize = compressedFiles.reduce((sum, item) => sum + item.original.size, 0); | |
| const totalCompressedSize = compressedFiles.reduce((sum, item) => sum + item.compressed.size, 0); | |
| const totalSavings = ((totalOriginalSize - totalCompressedSize) / totalOriginalSize * 100).toFixed(1); | |
| document.getElementById('compressResults').innerHTML = ` | |
| <div class="compression-stats"> | |
| <div class="stat-item"> | |
| <h5>${formatFileSize(totalOriginalSize)} | |
| <div class="stat-item"> | |
| {formatFileSize(totalCompressedSize)} | |
| <div class="stat-item"> | |
| {totalSavings} | |
| ${compressedFiles.map((item, index) => ` | |
| <div class="image-item"> | |
| <img class="image-preview" src="${URL.createObjectURL(item.compressed)}" alt="${item.compressed.name}"> | |
| <div class="image-info"> | |
| {item.compressed.name} | |
| {formatFileSize(item.original.size)} → ${formatFileSize(item.compressed.size)} (节省 ${item.compressionRatio} | |
| <div class="image-actions"> | |
| <button class="btn btn-primary btn-sm" onclick="downloadFile(${index})"> | |
| `).join('')} | |
| `; | |
| document.getElementById('resultsPanel').style.display = 'block'; | |
| } | |
| // 下载单个文件 | |
| function downloadFile(index) { | |
| const item = compressedFiles[index]; | |
| const url = URL.createObjectURL(item.compressed); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = item.compressed.name; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| // 下载所有文件 | |
| function downloadAll() { | |
| compressedFiles.forEach((item, index) => { | |
| setTimeout(() => downloadFile(index), index * 200); | |
| }); | |
| } | |
| // 重置工具 | |
| function resetTool() { | |
| selectedFiles = []; | |
| compressedFiles = []; | |
| document.getElementById('fileInput').value = ''; | |
| document.getElementById('settingsPanel').style.display = 'none'; | |
| document.getElementById('fileList').style.display = 'none'; | |
| document.getElementById('resultsPanel').style.display = 'none'; | |
| document.getElementById('imageItems').innerHTML = ''; | |
| document.getElementById('compressResults').innerHTML = ''; | |
| } | |
| // 格式化文件大小 | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 B'; | |
| const k = 1024; | |
| const sizes = ['B', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // 拖拽上传 | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const uploadArea = document.getElementById('uploadArea'); | |
| uploadArea.addEventListener('dragover', function(e) { | |
| e.preventDefault(); | |
| uploadArea.classList.add('dragover'); | |
| }); | |
| uploadArea.addEventListener('dragleave', function(e) { | |
| e.preventDefault(); | |
| uploadArea.classList.remove('dragover'); | |
| }); | |
| uploadArea.addEventListener('drop', function(e) { | |
| e.preventDefault(); | |
| uploadArea.classList.remove('dragover'); | |
| handleFiles(e.dataTransfer.files); | |
| }); | |
| uploadArea.addEventListener('click', selectFiles); | |
| }); | |
| </script> | |