Files
tools/image_compressor.html
2025-12-09 16:58:32 +08:00

1200 lines
32 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
margin: 20px;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.main-content {
display: flex;
gap: 20px;
align-items: flex-start;
}
.left-panel {
flex: 1;
min-width: 0;
max-width: 600px;
}
.right-panel {
flex: 1;
min-width: 0;
max-width: 600px;
}
h1 {
text-align: center;
margin-bottom: 30px;
font-weight: 600;
color: #333;
font-size: 2rem;
}
.upload-area {
border: 2px dashed #3498db;
border-radius: 8px;
padding: 40px 20px;
text-align: center;
margin-bottom: 30px;
background: #f8f9fa;
transition: all 0.3s ease;
cursor: pointer;
}
.upload-area:hover {
border-color: #2980b9;
background: #e3f2fd;
}
.upload-area.drag-over {
border-color: #27ae60;
background: #d4edda;
}
.upload-icon {
font-size: 48px;
color: #3498db;
margin-bottom: 15px;
}
.upload-text {
font-size: 16px;
color: #555;
margin-bottom: 10px;
}
.upload-hint {
font-size: 14px;
color: #888;
}
.file-select-btn {
background-color: #3498db;
color: white;
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: background-color 0.3s ease;
margin-top: 15px;
}
.file-select-btn:hover {
background-color: #2980b9;
}
input[type="file"] {
display: none;
}
.compression-options {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.option-group {
margin-bottom: 20px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: #3498db;
}
.quality-slider {
position: relative;
margin-top: 15px;
width: 100%;
overflow: hidden;
}
.quality-display {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.quality-label {
font-size: 14px;
color: #666;
}
.quality-value {
background: #3498db;
color: white;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
min-width: 40px;
text-align: center;
}
input[type="range"] {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
outline: none;
-webkit-appearance: none;
cursor: pointer;
transition: background 0.3s ease;
}
input[type="range"]:hover {
background: #d0d0d0;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: #3498db;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 6px rgba(52, 152, 219, 0.3);
transition: all 0.3s ease;
border: 2px solid white;
margin-right: -10px;
}
input[type="range"]::-webkit-slider-thumb:hover {
background: #2980b9;
transform: scale(1.1);
box-shadow: 0 3px 8px rgba(52, 152, 219, 0.4);
}
input[type="range"]::-webkit-slider-thumb:active {
transform: scale(0.95);
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: #3498db;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 6px rgba(52, 152, 219, 0.3);
transition: all 0.3s ease;
border: 2px solid white;
margin-right: -10px;
}
input[type="range"]::-moz-range-thumb:hover {
background: #2980b9;
transform: scale(1.1);
box-shadow: 0 3px 8px rgba(52, 152, 219, 0.4);
}
.quality-labels {
display: flex;
justify-content: space-between;
margin-top: 5px;
font-size: 11px;
color: #999;
}
.image-preview-container {
margin-top: 30px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.image-count {
font-size: 14px;
color: #666;
font-weight: normal;
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.image-item {
position: relative;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background: #f8f9fa;
transition: all 0.3s ease;
}
.image-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.image-preview {
width: 100%;
height: 120px;
object-fit: contain;
border-bottom: 1px solid #ddd;
cursor: pointer;
transition:
transform 0.3s ease,
box-shadow 0.3s ease;
background: #f8f9fa;
}
.image-preview:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.preview-hint {
position: absolute;
top: 5px;
left: 5px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: 10;
}
.image-item:hover .preview-hint {
opacity: 1;
}
/* 浮动预览遮罩层 */
.image-preview-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition:
opacity 0.3s ease,
visibility 0.3s ease;
}
.image-preview-overlay.show {
opacity: 1;
visibility: visible;
}
/* 预览容器 */
.preview-container {
position: relative;
max-width: 90%;
max-height: 90%;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
overflow: hidden;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.image-preview-overlay.show .preview-container {
transform: scale(1);
}
/* 预览标题栏 */
.preview-header {
background: #3498db;
color: white;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
}
.preview-title {
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 70%;
}
.preview-close {
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.3s ease;
}
.preview-close:hover {
background: rgba(255, 255, 255, 0.3);
}
/* 预览内容区 */
.preview-content {
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
max-height: 70vh;
background: white;
overflow: hidden;
position: relative;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
transform-origin: center center;
display: block;
}
/* 缩放控制 */
.preview-controls {
background: white;
padding: 15px 20px;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
.zoom-btn {
padding: 6px 12px;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
transition: background 0.3s ease;
}
.zoom-btn:hover {
background: #2980b9;
}
.zoom-display {
background: #f0f0f0;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
color: #666;
min-width: 50px;
text-align: center;
font-weight: 600;
}
.preview-info {
background: #e8f4fd;
border-left: 4px solid #3498db;
padding: 10px 15px;
margin-top: 10px;
font-size: 12px;
color: #555;
border-radius: 0 4px 4px 0;
}
.preview-info strong {
color: #2980b9;
}
.image-info {
padding: 8px;
font-size: 12px;
color: #666;
background: white;
}
.image-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 4px;
font-weight: 500;
color: #333;
}
.image-details {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.image-size {
color: #888;
}
.compression-ratio {
background: #27ae60;
color: white;
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
}
.image-actions {
display: flex;
gap: 5px;
}
.image-action-btn {
flex: 1;
padding: 4px 8px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 10px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-download {
background: #27ae60;
color: white;
}
.btn-download:hover {
background: #229954;
}
.btn-delete {
background: #e74c3c;
color: white;
}
.btn-delete:hover {
background: #c0392b;
}
.action-buttons {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 30px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s ease;
min-width: 120px;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
}
.btn-secondary {
background-color: #95a5a6;
color: white;
}
.btn-secondary:hover {
background-color: #7f8c8d;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #888;
}
.empty-state-icon {
font-size: 64px;
margin-bottom: 20px;
opacity: 0.5;
}
.empty-state-text {
font-size: 16px;
margin-bottom: 10px;
}
.empty-state-hint {
font-size: 14px;
color: #aaa;
}
.progress-container {
display: none;
margin-top: 20px;
}
.progress-bar {
width: 100%;
height: 8px;
background: #ecf0f1;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #3498db, #2980b9);
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
margin-top: 10px;
font-size: 14px;
color: #666;
}
.download-all-btn {
background-color: #27ae60;
color: white;
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: background-color 0.3s ease;
min-width: 120px;
display: none;
}
.download-all-btn:hover {
background-color: #229954;
}
@media (max-width: 1200px) {
.main-content {
flex-direction: column;
}
.left-panel,
.right-panel {
max-width: none;
}
}
@media (max-width: 768px) {
.container {
margin: 10px;
padding: 15px;
}
.image-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.option-group {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.btn {
width: 100%;
}
}
@media (max-width: 480px) {
.image-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<div class="container">
<h1>图片压缩工具(纯前端,后台不保存数据)</h1>
<div class="main-content">
<!-- 左侧面板 -->
<div class="left-panel">
<!-- 上传区域 -->
<div class="upload-area" id="uploadArea">
<div class="upload-icon">🖼️</div>
<div class="upload-text">点击或拖拽图片到这里</div>
<div class="upload-hint">支持 JPG、PNG、WEBP 格式</div>
<button class="file-select-btn">选择图片</button>
<input
type="file"
id="fileInput"
multiple
accept="image/jpeg,image/png,image/webp"
/>
</div>
<!-- 压缩选项 -->
<div class="compression-options">
<div class="option-group">
<div class="form-group">
<label for="quality">压缩质量:</label>
<div class="quality-slider">
<div class="quality-display">
<span class="quality-label">压缩质量</span>
<span class="quality-value" id="qualityValue">80%</span>
</div>
<input
type="range"
id="quality"
min="10"
max="100"
value="80"
step="5"
/>
<div class="quality-labels">
<span>低质量</span>
<span>高质量</span>
</div>
</div>
</div>
<div class="form-group" style="margin-top: 20px">
<label for="format">输出格式:</label>
<select id="format">
<option value="original">保持原格式</option>
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
<option value="webp">WEBP</option>
</select>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons" id="actionButtons" style="display: none">
<button class="btn btn-primary" id="compressAllBtn">
压缩所有图片
</button>
<button class="btn btn-secondary" id="clearAllBtn">清空图片</button>
<button
class="btn btn-primary download-all-btn"
id="downloadAllBtn"
>
下载所有压缩后的图片
</button>
</div>
<!-- 进度条 -->
<div class="progress-container" id="progressContainer">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text" id="progressText">处理中...</div>
</div>
</div>
<!-- 右侧面板 -->
<div class="right-panel">
<!-- 空状态 -->
<div class="empty-state" id="emptyState">
<div class="empty-state-icon">📷</div>
<div class="empty-state-text">还没有选择图片</div>
<div class="empty-state-hint">点击左侧区域选择要压缩的图片</div>
</div>
<!-- 图片预览容器 -->
<div
class="image-preview-container"
id="imagePreviewContainer"
style="display: none"
>
<div class="section-title">
<span>压缩结果</span>
<span class="image-count" id="imageCount">0 张图片</span>
</div>
<div class="image-grid" id="imageGrid"></div>
</div>
</div>
</div>
</div>
<!-- 浮动图片预览 -->
<div class="image-preview-overlay" id="imagePreviewOverlay">
<div class="preview-container" id="previewContainer">
<div class="preview-header">
<div class="preview-title" id="previewTitle">图片预览</div>
<button class="preview-close" id="previewClose">×</button>
</div>
<div class="preview-content">
<img src="" alt="" class="preview-image" id="previewImage" />
</div>
<div class="preview-controls">
<button class="zoom-btn" onclick="setZoom(0.5)">50%</button>
<button class="zoom-btn" onclick="setZoom(1)">100%</button>
<button class="zoom-btn" onclick="setZoom(2)">200%</button>
<div class="zoom-display" id="zoomDisplay">100%</div>
</div>
<div class="preview-info">
<strong>💡 查看提示:</strong> 100% 查看实际清晰度 | 点击背景或 ×
关闭预览 | 键盘快捷键:+/- 缩放1 重置为100%
</div>
</div>
</div>
<script>
// 全局变量
let uploadedImages = [];
// DOM元素
const uploadArea = document.getElementById("uploadArea");
const fileInput = document.getElementById("fileInput");
const emptyState = document.getElementById("emptyState");
const imagePreviewContainer = document.getElementById(
"imagePreviewContainer",
);
const imageGrid = document.getElementById("imageGrid");
const imageCount = document.getElementById("imageCount");
const actionButtons = document.getElementById("actionButtons");
const compressAllBtn = document.getElementById("compressAllBtn");
const clearAllBtn = document.getElementById("clearAllBtn");
const downloadAllBtn = document.getElementById("downloadAllBtn");
const progressContainer = document.getElementById("progressContainer");
const progressFill = document.getElementById("progressFill");
const progressText = document.getElementById("progressText");
const qualitySlider = document.getElementById("quality");
const qualityValue = document.getElementById("qualityValue");
// 浮动预览相关DOM元素
const imagePreviewOverlay = document.getElementById(
"imagePreviewOverlay",
);
const previewContainer = document.getElementById("previewContainer");
const previewTitle = document.getElementById("previewTitle");
const previewImage = document.getElementById("previewImage");
const previewClose = document.getElementById("previewClose");
const zoomDisplay = document.getElementById("zoomDisplay");
// 浮动预览相关变量
let currentZoom = 1;
// 事件监听器
uploadArea.addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", handleFileSelect);
compressAllBtn.addEventListener("click", compressAllImages);
clearAllBtn.addEventListener("click", clearAllImages);
downloadAllBtn.addEventListener("click", downloadAllImages);
// 质量滑块事件
qualitySlider.addEventListener("input", (e) => {
qualityValue.textContent = e.target.value + "%";
});
// 浮动预览事件监听器
previewClose.addEventListener("click", hideImagePreview);
imagePreviewOverlay.addEventListener("click", function (e) {
if (e.target === imagePreviewOverlay) {
hideImagePreview();
}
});
// 键盘事件监听器
document.addEventListener("keydown", function (e) {
if (imagePreviewOverlay.classList.contains("show")) {
if (e.key === "Escape") {
hideImagePreview();
} else if (e.key === "+" || e.key === "=") {
setZoom(Math.min(currentZoom * 1.2, 5));
} else if (e.key === "-" || e.key === "_") {
setZoom(Math.max(currentZoom * 0.8, 0.1));
} else if (e.key === "1") {
setZoom(1);
}
}
});
// 拖拽功能
uploadArea.addEventListener("dragover", handleDragOver);
uploadArea.addEventListener("dragleave", handleDragLeave);
uploadArea.addEventListener("drop", handleDrop);
function handleDragOver(e) {
e.preventDefault();
uploadArea.classList.add("drag-over");
}
function handleDragLeave(e) {
e.preventDefault();
uploadArea.classList.remove("drag-over");
}
function handleDrop(e) {
e.preventDefault();
uploadArea.classList.remove("drag-over");
const files = Array.from(e.dataTransfer.files).filter((file) =>
file.type.startsWith("image/"),
);
if (files.length > 0) {
processFiles(files);
}
}
function handleFileSelect(e) {
const files = Array.from(e.target.files);
processFiles(files);
e.target.value = "";
}
function processFiles(files) {
files.forEach((file) => {
if (!file.type.startsWith("image/")) {
alert(`文件 ${file.name} 不是有效的图片格式`);
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const imageObj = {
id: Date.now() + Math.random(),
name: file.name,
originalSize: file.size,
originalSizeFormatted: formatFileSize(file.size),
dataUrl: e.target.result,
type: file.type,
compressed: false,
compressedDataUrl: null,
compressedSize: null,
compressionRatio: null,
};
uploadedImages.push(imageObj);
updateUI();
};
reader.readAsDataURL(file);
});
}
function formatFileSize(bytes) {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
function updateUI() {
const hasImages = uploadedImages.length > 0;
emptyState.style.display = hasImages ? "none" : "block";
imagePreviewContainer.style.display = hasImages ? "block" : "none";
actionButtons.style.display = hasImages ? "flex" : "none";
// 更新图片计数
imageCount.textContent = `${uploadedImages.length} 张图片`;
renderImageGrid();
updateDownloadAllButton();
}
function renderImageGrid() {
imageGrid.innerHTML = "";
uploadedImages.forEach((image, index) => {
const imageItem = document.createElement("div");
imageItem.className = "image-item";
const displayDataUrl = image.compressed
? image.compressedDataUrl
: image.dataUrl;
const displaySize = image.compressed
? formatFileSize(image.compressedSize)
: image.originalSizeFormatted;
const previewText = image.compressed ? "查看压缩效果" : "查看原图";
imageItem.innerHTML = `
<div style="position: relative;">
<img src="${displayDataUrl}" alt="${image.name}" class="image-preview" onclick="showImagePreview('${displayDataUrl}', '${image.name}')">
<span class="preview-hint">${previewText}</span>
</div>
<div class="image-info">
<div class="image-name">${image.name}</div>
<div class="image-details">
<span class="image-size">${displaySize}</span>
${image.compressionRatio ? `<span class="compression-ratio">压缩 ${image.compressionRatio}%</span>` : ""}
</div>
<div class="image-actions">
${
image.compressed
? `
<button class="image-action-btn btn-download" onclick="downloadImage(${index})">下载</button>
<button class="image-action-btn btn-delete" onclick="deleteImage(${index})">删除</button>
`
: '<span style="color: #888; font-size: 10px;">等待压缩</span>'
}
</div>
</div>
`;
imageGrid.appendChild(imageItem);
});
}
function updateDownloadAllButton() {
const hasCompressedImages = uploadedImages.some(
(img) => img.compressed,
);
downloadAllBtn.style.display = hasCompressedImages ? "block" : "none";
}
// 浮动预览功能
function showImagePreview(dataUrl, fileName) {
previewImage.src = dataUrl;
previewTitle.textContent = fileName;
currentZoom = 1;
setZoom(1);
imagePreviewOverlay.classList.add("show");
document.body.style.overflow = "hidden"; // 防止背景滚动
}
function hideImagePreview() {
imagePreviewOverlay.classList.remove("show");
document.body.style.overflow = ""; // 恢复滚动
}
function setZoom(scale) {
currentZoom = scale;
previewImage.style.transform = `scale(${scale})`;
zoomDisplay.textContent = Math.round(scale * 100) + "%";
}
async function compressAllImages() {
if (uploadedImages.length === 0) {
alert("请先选择图片");
return;
}
showProgress(true);
updateProgress(0, "开始压缩图片...");
const quality = parseInt(qualitySlider.value) / 100;
const format = document.getElementById("format").value;
for (let i = 0; i < uploadedImages.length; i++) {
updateProgress(
(i / uploadedImages.length) * 90,
`正在压缩第 ${i + 1}/${uploadedImages.length} 张图片...`,
);
const imageObj = uploadedImages[i];
const compressedResult = await compressImage(
imageObj,
quality,
format,
);
if (compressedResult) {
imageObj.compressedDataUrl = compressedResult.dataUrl;
imageObj.compressedSize = compressedResult.size;
imageObj.compressionRatio = Math.round(
(1 - compressedResult.size / imageObj.originalSize) * 100,
);
imageObj.compressed = true;
}
}
updateProgress(100, "压缩完成!");
updateUI(); // 重新渲染UI以显示压缩后的图片
setTimeout(() => {
showProgress(false);
}, 2000);
}
async function compressImage(imageObj, quality, outputFormat) {
return new Promise((resolve) => {
const img = new Image();
img.onload = function () {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// 保持原始尺寸
canvas.width = img.width;
canvas.height = img.height;
// 绘制图片
ctx.drawImage(img, 0, 0, img.width, img.height);
// 确定输出格式
let format = outputFormat;
if (format === "original") {
format = imageObj.type.includes("jpeg")
? "jpeg"
: imageObj.type.includes("png")
? "png"
: "jpeg";
}
// 转换为 Blob 然后计算大小
canvas.toBlob(
(blob) => {
if (blob) {
const reader = new FileReader();
reader.onload = function (e) {
resolve({
dataUrl: e.target.result,
size: blob.size,
});
};
reader.readAsDataURL(blob);
} else {
resolve(null);
}
},
`image/${format}`,
quality,
);
};
img.src = imageObj.dataUrl;
});
}
function downloadImage(index) {
const imageObj = uploadedImages[index];
if (!imageObj.compressed) {
alert("图片还未压缩");
return;
}
const link = document.createElement("a");
link.download = `compressed_${imageObj.name}`;
link.href = imageObj.compressedDataUrl;
link.click();
}
async function downloadAllImages() {
const compressedImages = uploadedImages.filter((img) => img.compressed);
if (compressedImages.length === 0) {
alert("没有压缩后的图片可下载");
return;
}
for (let i = 0; i < compressedImages.length; i++) {
setTimeout(() => {
const imageObj = compressedImages[i];
const link = document.createElement("a");
link.download = `compressed_${imageObj.name}`;
link.href = imageObj.compressedDataUrl;
link.click();
}, i * 500); // 间隔500ms下载避免浏览器阻止多个下载
}
}
function deleteImage(index) {
uploadedImages.splice(index, 1);
updateUI();
}
function clearAllImages() {
if (confirm("确定要清空所有图片吗?")) {
uploadedImages = [];
fileInput.value = "";
updateUI();
}
}
function showProgress(show) {
progressContainer.style.display = show ? "block" : "none";
compressAllBtn.disabled = show;
clearAllBtn.disabled = show;
}
function updateProgress(percent, text) {
progressFill.style.width = percent + "%";
progressText.textContent = text;
}
// 初始化
updateUI();
</script>
</body>
</html>