Toolhub / Views /Tools /ImageCompressor.cshtml
unifare
Initial commit: ToolHub ASP.NET Core app
5fc700d
@{
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">
<div style="font-weight: 600; margin-bottom: 0.25rem;">${file.name}</div>
<div style="font-size: 0.875rem; color: var(--dark-2);">
${formatFileSize(file.size)} • ${file.type}
</div>
<div class="progress-bar" id="progress-${index}" style="display: none;">
<div class="progress-fill" id="progress-fill-${index}"></div>
</div>
</div>
<div class="image-actions">
<button class="btn btn-outline btn-sm" onclick="removeFile(${index})">
<i class="fas fa-times"></i>
</button>
</div>
`;
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 = '<i class="fas fa-compress"></i> 开始压缩';
}
// 压缩单个图片
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)}</h5>
<p>原始大小</p>
</div>
<div class="stat-item">
<h5>${formatFileSize(totalCompressedSize)}</h5>
<p>压缩后大小</p>
</div>
<div class="stat-item">
<h5>${totalSavings}%</h5>
<p>压缩率</p>
</div>
</div>
${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">
<div style="font-weight: 600; margin-bottom: 0.25rem;">${item.compressed.name}</div>
<div style="font-size: 0.875rem; color: var(--dark-2);">
${formatFileSize(item.original.size)} → ${formatFileSize(item.compressed.size)} (节省 ${item.compressionRatio}%)
</div>
</div>
<div class="image-actions">
<button class="btn btn-primary btn-sm" onclick="downloadFile(${index})">
<i class="fas fa-download"></i> 下载
</button>
</div>
</div>
`).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>