<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端图片压缩工具</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
line-height: 1.6;
background: #f5f7fa;
padding: 20px;
}
.container { max-width: 800px; margin: 0 auto; }
header { text-align: center; margin-bottom: 30px; }
h1 { color: #2c3e50; margin-bottom: 10px; }
.main-content { display: flex; flex-direction: column; gap: 20px; }
.upload-area {
border: 2px dashed #3498db; border-radius: 10px; padding: 30px;
text-align: center; cursor: pointer; background: white;
}
.upload-area:hover { border-color: #2980b9; background: #f8fafc; }
.upload-icon { font-size: 40px; color: #3498db; margin-bottom: 10px; }
.controls { display: flex; flex-direction: column; gap: 15px; background: white; padding: 20px; border-radius: 10px; }
.control-group { display: flex; flex-direction: column; gap: 5px; }
.control-group label { font-weight: 600; color: #2c3e50; }
input[type="range"] { width: 100%; height: 6px; }
.preview-container { display: flex; flex-wrap: wrap; gap: 20px; }
.preview-box { flex: 1; min-width: 300px; background: white; padding: 20px; border-radius: 10px; }
.image-box { border: 1px solid #ddd; border-radius: 8px; min-height: 200px; display: flex; align-items: center; justify-content: center; }
.image-box img { max-width: 100%; max-height: 200px; }
.image-info { display: flex; justify-content: space-between; margin-top: 10px; }
.btn-group { display: flex; gap: 10px; margin-top: 20px; }
.btn { flex: 1; padding: 12px; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
.btn-primary { background: #3498db; color: white; }
.btn-success { background: #2ecc71; color: white; }
.btn:disabled { background: #bdc3c7; cursor: not-allowed; }
.status { padding: 10px; border-radius: 6px; text-align: center; margin-top: 10px; display: none; }
.status.success { background: #d4edda; color: #155724; }
.status.error { background: #f8d7da; color: #721c24; }
footer { text-align: center; margin-top: 30px; color: #7f8c8d; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>前端图片压缩工具</h1>
<p>基于Blob的纯前端图片处理方案</p>
</header>
<div class="main-content">
<div class="upload-area" id="dropArea">
<div class="upload-icon">📁</div>
<p>点击或拖拽上传图片</p>
<input type="file" id="fileInput" accept="image/*" hidden>
</div>
<div class="controls">
<div class="control-group">
<label>压缩质量: <span id="qualityValue">80%</span></label>
<input type="range" id="quality" min="10" max="100" value="80">
</div>
<div class="control-group">
<label>最大宽度: <span id="widthValue">800px</span></label>
<input type="range" id="maxWidth" min="200" max="2000" value="800" step="100">
</div>
</div>
<div class="preview-container">
<div class="preview-box">
<h3>原图</h3>
<div class="image-box">
<img id="originalPreview" style="display:none;">
<p id="originalPlaceholder">等待上传图片</p>
</div>
<div class="image-info">
<span>尺寸: <span id="originalDimensions">-</span></span>
<span>大小: <span id="originalSize">-</span></span>
</div>
</div>
<div class="preview-box">
<h3>压缩后</h3>
<div class="image-box">
<img id="compressedPreview" style="display:none;">
<p id="compressedPlaceholder">等待压缩</p>
</div>
<div class="image-info">
<span>尺寸: <span id="compressedDimensions">-</span></span>
<span>大小: <span id="compressedSize">-</span></span>
<span>压缩: <span id="compressionRatio">-</span></span>
</div>
</div>
</div>
<div class="btn-group">
<button id="compressBtn" class="btn btn-primary" disabled>开始压缩</button>
<button id="downloadBtn" class="btn btn-success" disabled>下载图片</button>
</div>
<div id="statusMessage" class="status"></div>
</div>
<footer>
<p>纯前端图片压缩方案</p>
</footer>
</div>
<script>
// DOM元素
const fileInput = document.getElementById('fileInput');
const dropArea = document.getElementById('dropArea');
const compressBtn = document.getElementById('compressBtn');
const downloadBtn = document.getElementById('downloadBtn');
const qualitySlider = document.getElementById('quality');
const maxWidthSlider = document.getElementById('maxWidth');
const qualityValue = document.getElementById('qualityValue');
const widthValue = document.getElementById('widthValue');
const originalPreview = document.getElementById('originalPreview');
const compressedPreview = document.getElementById('compressedPreview');
const originalPlaceholder = document.getElementById('originalPlaceholder');
const compressedPlaceholder = document.getElementById('compressedPlaceholder');
const originalDimensions = document.getElementById('originalDimensions');
const originalSize = document.getElementById('originalSize');
const compressedDimensions = document.getElementById('compressedDimensions');
const compressedSize = document.getElementById('compressedSize');
const compressionRatio = document.getElementById('compressionRatio');
const statusMessage = document.getElementById('statusMessage');
// 状态变量
let originalFile = null;
let originalImageData = null;
let compressedBlob = null;
// 工具函数
function formatFileSize(bytes) {
if (!bytes) return '-';
const units = ['B', 'KB', 'MB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
function showStatus(message, isError = false) {
statusMessage.textContent = message;
statusMessage.className = `status ${isError ? 'error' : 'success'}`;
statusMessage.style.display = 'block';
setTimeout(() => { statusMessage.style.display = 'none'; }, 3000);
}
// 事件监听
qualitySlider.addEventListener('input', () => qualityValue.textContent = `${qualitySlider.value}%`);
maxWidthSlider.addEventListener('input', () => widthValue.textContent = `${maxWidthSlider.value}px`);
dropArea.addEventListener('click', () => fileInput.click());
dropArea.addEventListener('dragover', (e) => e.preventDefault());
dropArea.addEventListener('drop', (e) => {
e.preventDefault();
if (e.dataTransfer.files.length) handleFileSelect(e.dataTransfer.files[0]);
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) handleFileSelect(e.target.files[0]);
});
// 处理文件选择
function handleFileSelect(file) {
if (!file.type.startsWith('image/')) {
showStatus('请选择图片文件!', true);
return;
}
originalFile = file;
const reader = new FileReader();
reader.onload = (e) => {
// 显示原图
originalPreview.src = e.target.result;
originalPreview.style.display = 'block';
originalPlaceholder.style.display = 'none';
originalSize.textContent = formatFileSize(file.size);
// 获取图片尺寸
const img = new Image();
img.onload = () => {
originalDimensions.textContent = `${img.width} × ${img.height}`;
originalImageData = { width: img.width, height: img.height, size: file.size };
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
compressBtn.disabled = false;
showStatus('图片已加载,可以开始压缩');
}
// 图片压缩函数
function compressImage(file, quality, maxWidth) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
const img = new Image();
img.src = e.target.result;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算缩放比例
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// Canvas转Blob
canvas.toBlob(
(blob) => resolve({ blob, width, height }),
'image/jpeg',
quality / 100
);
};
};
});
}
// 压缩按钮事件
compressBtn.addEventListener('click', async () => {
if (!originalFile) return;
compressBtn.disabled = true;
compressBtn.textContent = '压缩中...';
try {
const quality = parseInt(qualitySlider.value);
const maxWidth = parseInt(maxWidthSlider.value);
const result = await compressImage(originalFile, quality, maxWidth);
compressedBlob = result.blob;
// 显示压缩图片
compressedPreview.src = URL.createObjectURL(compressedBlob);
compressedPreview.style.display = 'block';
compressedPlaceholder.style.display = 'none';
compressedDimensions.textContent = `${result.width} × ${result.height}`;
compressedSize.textContent = formatFileSize(compressedBlob.size);
const ratio = ((originalImageData.size - compressedBlob.size) / originalImageData.size * 100).toFixed(1);
compressionRatio.textContent = `${ratio}%`;
downloadBtn.disabled = false;
showStatus(`压缩完成!节省了 ${ratio}% 空间`);
} catch (error) {
showStatus('压缩失败,请重试', true);
} finally {
compressBtn.disabled = false;
compressBtn.textContent = '开始压缩';
}
});
// 下载按钮事件
downloadBtn.addEventListener('click', () => {
if (!compressedBlob) return;
const url = URL.createObjectURL(compressedBlob);
const link = document.createElement('a');
link.href = url;
link.download = `compressed-${originalFile.name.replace(/\.[^/.]+$/, "")}.jpg`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => URL.revokeObjectURL(url), 100);
showStatus('开始下载压缩图片');
});
// 初始化
document.addEventListener('DOMContentLoaded', () => {
showStatus('上传图片开始压缩');
});
</script>
</body>
</html>
效果图如下

本文转载至@程序员大华,原文地址

1786

被折叠的 条评论
为什么被折叠?



