1200 lines
32 KiB
HTML
1200 lines
32 KiB
HTML
<!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> |