<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片处理多卡片</title>
<!-- 引入Font Awesome图标 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* 基础样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
body {
background: linear-gradient(to bottom, #f8fafc, #f1f5f9);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
color: #334155;
overflow-x: hidden;
}
.container {
width: 100%;
max-width: 400px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
font-size: 2rem;
font-weight: 700;
color: #0f172a;
margin-bottom: 2rem;
text-align: center;
}
/* 卡片堆叠容器 */
.card-stack-container {
width: 100%;
height: 600px;
position: relative;
overflow: visible;
}
/* 卡片堆叠 */
.card-stack {
width: 100%;
height: 100%;
position: relative;
transition: transform 0.5s ease;
}
/* 卡片容器 */
.card-container {
width: 100%;
height: 600px;
position: absolute;
top: 0;
left: 0;
perspective: 1000px;
transition: all 0.5s ease;
opacity: 0;
visibility: hidden;
transform: translateY(40px) scale(0.9);
}
.card-container.active {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
z-index: 10;
}
.card-container.prev {
opacity: 0.7;
visibility: visible;
transform: translateY(-40px) scale(0.9);
z-index: 5;
}
.card-container.next {
opacity: 0.7;
visibility: visible;
transform: translateY(40px) scale(0.9);
z-index: 5;
}
/* 卡片 */
.card {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.8s;
border-radius: 1rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.card.flipped {
transform: rotateY(180deg);
}
/* 卡片面 */
.card-face {
width: 100%;
height: 100%;
position: absolute;
backface-visibility: hidden;
border-radius: 1rem;
overflow: hidden;
}
/* 卡片正面 */
.card-front {
display: flex;
flex-direction: column;
padding: 1.5rem;
color: white;
}
/* 卡片背面 - 注意这里添加了rotateY(180deg)来修复颠倒问题 */
.card-back {
background: white;
transform: rotateY(180deg);
display: flex;
flex-direction: column;
padding: 1.5rem;
}
/* 卡片头部 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.icon-container {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.settings-icon {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s;
}
.settings-icon:hover {
background-color: rgba(255, 255, 255, 0.3);
}
/* 卡片标题 */
.card-title {
margin-bottom: 1rem;
}
.card-title h2 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 0.25rem;
}
.card-title h3 {
font-size: 1rem;
font-weight: 500;
opacity: 0.8;
}
/* 卡片图像 */
.card-image {
margin-bottom: 1rem;
flex-grow: 1;
}
.image-container {
width: 100%;
height: 100%;
border-radius: 0.75rem;
background-size: cover;
background-position: center;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
/* 卡片底部 */
.card-footer {
margin-top: auto;
}
.tag {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 9999px;
margin-bottom: 0.75rem;
font-size: 0.875rem;
}
.card-description {
font-size: 0.875rem;
opacity: 0.9;
}
/* 卡片背面头部 */
.card-back-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid #e2e8f0;
}
.card-back-header h2 {
font-size: 1.25rem;
font-weight: 600;
color: #1e40af;
}
.close-icon {
width: 2rem;
height: 2rem;
border-radius: 50%;
background-color: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s;
}
.close-icon:hover {
background-color: #e2e8f0;
}
/* 卡片背面内容 */
.card-back-content {
flex-grow: 1;
overflow-y: auto;
padding-right: 0.5rem;
margin-right: -0.5rem;
}
/* 上传区域 */
.upload-area {
border: 2px dashed #cbd5e1;
border-radius: 0.75rem;
padding: 1.5rem;
text-align: center;
cursor: pointer;
transition: all 0.2s;
margin-bottom: 1.5rem;
}
.upload-area:hover {
background-color: #f8fafc;
border-color: #94a3b8;
}
.upload-area i {
font-size: 2rem;
color: #64748b;
margin-bottom: 0.75rem;
}
.upload-area p {
margin-bottom: 0.25rem;
color: #334155;
}
.upload-area .upload-hint {
font-size: 0.75rem;
color: #64748b;
}
/* 预览区域 */
.preview-container {
margin-bottom: 1.5rem;
}
.preview-container h3 {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: #334155;
}
.preview-image-container {
width: 100%;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
overflow: hidden;
background-color: #f8fafc;
}
.preview-image-container img {
width: 100%;
height: auto;
display: block;
}
/* 设置区域 */
.settings-section {
background-color: #f8fafc;
border-radius: 0.75rem;
padding: 1rem;
margin-bottom: 1rem;
border: 1px solid #e2e8f0;
}
.settings-section h3 {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.75rem;
color: #1e40af;
padding-bottom: 0.5rem;
border-bottom: 1px solid #e2e8f0;
}
/* 选择控件 */
.select-control {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #cbd5e1;
font-size: 0.875rem;
background-color: white;
margin-bottom: 0.75rem;
color: #334155;
}
/* 范围控件 */
.range-info {
display: flex;
justify-content: space-between;
font-size: 0.75rem;
margin-bottom: 0.25rem;
color: #64748b;
}
.range-control {
width: 100%;
height: 6px;
-webkit-appearance: none;
appearance: none;
background: #e2e8f0;
border-radius: 3px;
outline: none;
margin-bottom: 1rem;
}
.range-control::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
background: #3b82f6;
border-radius: 50%;
cursor: pointer;
}
/* 尺寸输入 */
.dimension-inputs {
display: flex;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.dimension-input {
flex: 1;
}
.dimension-input label {
display: block;
font-size: 0.75rem;
margin-bottom: 0.25rem;
color: #64748b;
}
.text-input {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #cbd5e1;
font-size: 0.875rem;
background-color: white;
}
/* 复选框控件 */
.checkbox-control {
display: flex;
align-items: center;
margin-bottom: 0.75rem;
padding: 0.5rem;
background-color: #f1f5f9;
border-radius: 0.5rem;
}
.checkbox-control input {
margin-right: 0.5rem;
width: 1rem;
height: 1rem;
accent-color: #3b82f6;
}
.checkbox-control label {
font-size: 0.875rem;
color: #334155;
}
/* 滤镜控件 */
.filter-control {
margin-bottom: 0.75rem;
}
/* 压缩预设 */
.compression-presets {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.compression-preset {
flex: 1;
padding: 0.5rem;
border-radius: 0.5rem;
font-size: 0.875rem;
text-align: center;
cursor: pointer;
background-color: white;
border: 1px solid #cbd5e1;
color: #64748b;
transition: all 0.2s;
}
.compression-preset:hover:not(.active) {
background-color: #f1f5f9;
color: #334155;
}
.compression-preset.active {
background-color: #3b82f6;
color: white;
border-color: #3b82f6;
}
/* 压缩信息 */
.compression-info {
background-color: white;
padding: 0.75rem;
border-radius: 0.5rem;
font-size: 0.75rem;
border: 1px solid #e2e8f0;
}
.compression-info div {
display: flex;
justify-content: space-between;
margin-bottom: 0.25rem;
}
.compression-info div:last-child {
margin-bottom: 0;
}
/* 卡片背面底部 */
.card-back-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding-top: 1rem;
border-top: 1px solid #e2e8f0;
margin-top: 1rem;
}
/* 按钮 */
.button {
padding: 0.75rem 1rem;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 0.5rem;
border: none;
}
.button-secondary {
background-color: white;
border: 1px solid #cbd5e1;
color: #334155;
}
.button-secondary:hover {
background-color: #f1f5f9;
}
.button-primary {
background-color: #3b82f6;
color: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.button-primary:hover {
background-color: #2563eb;
}
.button-primary:disabled {
background-color: #93c5fd;
cursor: not-allowed;
}
/* 通知 */
.notification {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
padding: 1rem 1.5rem;
border-radius: 0.5rem;
background-color: white;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
z-index: 1000;
opacity: 0;
transform: translateY(1rem);
transition: all 0.3s;
max-width: 20rem;
}
.notification.show {
opacity: 1;
transform: translateY(0);
}
.notification.success {
border-left: 4px solid #10b981;
}
.notification.error {
border-left: 4px solid #ef4444;
}
.notification.info {
border-left: 4px solid #3b82f6;
}
/* 滚动条样式 */
.card-back-content::-webkit-scrollbar {
width: 6px;
}
.card-back-content::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 3px;
}
.card-back-content::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.card-back-content::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* 导航指示器 */
.navigation-indicators {
display: flex;
justify-content: center;
margin-top: 1.5rem;
}
.indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #cbd5e1;
margin: 0 4px;
transition: all 0.3s;
}
.indicator.active {
background-color: #3b82f6;
transform: scale(1.5);
}
/* 滑动指示器 */
.swipe-indicator {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
color: white;
opacity: 0.7;
z-index: 20;
}
.swipe-indicator i {
font-size: 1.5rem;
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-10px);
}
60% {
transform: translateY(-5px);
}
}
</style>
</head>
<body>
<div class="container">
<h1>图片处理工具</h1>
<div class="card-stack-container">
<div class="card-stack" id="cardStack">
<!-- 卡片1:图片格式转换 -->
<div class="card-container" data-index="0">
<div class="card" id="card-0">
<!-- 卡片正面 -->
<div class="card-face card-front" style="background: linear-gradient(to bottom, #3b82f6, #1d4ed8);">
<div class="card-header">
<div class="icon-container">
<i class="fas fa-image"></i>
</div>
<div class="settings-icon flip-to-back">
<i class="fas fa-cog"></i>
</div>
</div>
<div class="card-title">
<h2>图片格式转换</h2>
<h3>支持多种格式</h3>
</div>
<div class="card-image">
<div class="image-container" style="background-image: url('https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2760&q=80');"></div>
</div>
<div class="card-footer">
<div class="tag">
<i class="fas fa-image"></i>
<span>支持多种格式</span>
</div>
<p class="card-description">将图片转换为JPEG、PNG、WebP等格式,调整大小、质量和应用滤镜。</p>
</div>
</div>
<!-- 卡片背面 -->
<div class="card-face card-back">
<div class="card-back-header">
<h2>图片格式转换设置</h2>
<div class="close-icon flip-to-front">
<i class="fas fa-times"></i>
</div>
</div>
<div class="card-back-content">
<!-- 上传区域 -->
<div class="upload-area" data-card="0">
<i class="fas fa-cloud-upload-alt"></i>
<p>拖放图片或点击上传</p>
<p class="upload-hint">支持 JPG, PNG, GIF, WEBP</p>
<input type="file" class="file-input" accept="image/*" style="display: none;">
</div>
<!-- 预览区域 -->
<div class="preview-container" data-card="0" style="display: none;">
<h3>预览</h3>
<div class="preview-image-container">
<img class="preview-image" src="/placeholder.svg" alt="预览">
</div>
</div>
<!-- 格式选择 -->
<div class="settings-section">
<h3>输出格式</h3>
<select class="format-select select-control" data-card="0">
<option value="jpeg">JPEG (高兼容性)</option>
<option value="png">PNG (支持透明)</option>
<option value="webp">WEBP (高压缩率)</option>
<option value="gif">GIF (动画支持)</option>
</select>
<div class="quality-container" data-card="0">
<div class="range-info">
<span>质量</span>
<span class="quality-value">80%</span>
</div>
<input type="range" class="quality-range range-control" min="1" max="100" value="80">
</div>
</div>
<!-- 图片比例选择 -->
<div class="settings-section">
<h3>调整大小</h3>
<div class="dimension-inputs">
<div class="dimension-input">
<label>宽度</label>
<input type="number" class="width-input text-input" data-card="0" placeholder="宽度">
</div>
<div class="dimension-input">
<label>高度</label>
<input type="number" class="height-input text-input" data-card="0" placeholder="高度">
</div>
</div>
<div class="checkbox-control">
<input type="checkbox" class="lock-ratio-checkbox" data-card="0" checked>
<label>锁定宽高比</label>
</div>
<select class="aspect-ratio-select select-control" data-card="0">
<option value="original">原始比例</option>
<option value="free">自由比例</option>
<option value="1:1">1:1 (正方形)</option>
<option value="4:3">4:3 (标准)</option>
<option value="16:9">16:9 (宽屏)</option>
<option value="9:16">9:16 (竖屏)</option>
</select>
</div>
<!-- 图像调整 -->
<div class="settings-section">
<h3>图像调整</h3>
<div class="filter-control">
<div class="range-info">
<span>亮度</span>
<span class="brightness-value">0%</span>
</div>
<input type="range" class="brightness-range range-control" data-card="0" min="-100" max="100" value="0">
</div>
<div class="filter-control">
<div class="range-info">
<span>对比度</span>
<span class="contrast-value">0%</span>
</div>
<input type="range" class="contrast-range range-control" data-card="0" min="-100" max="100" value="0">
</div>
<div class="filter-control">
<div class="range-info">
<span>饱和度</span>
<span class="saturation-value">0%</span>
</div>
<input type="range" class="saturation-range range-control" data-card="0" min="-100" max="100" value="0">
</div>
</div>
<!-- 压缩选项 -->
<div class="settings-section">
<h3>压缩选项</h3>
<div class="compression-presets" data-card="0">
<button class="compression-preset active" data-preset="balanced">平衡</button>
<button class="compression-preset" data-preset="high-quality">高质量</button>
<button class="compression-preset" data-preset="small-size">小体积</button>
</div>
<div class="compression-info">
<div>
<span>预计输出大小:</span>
<span class="size-estimate">未知</span>
</div>
<div>
<span>预计节省空间:</span>
<span class="size-saving">未知</span>
</div>
</div>
</div>
</div>
<div class="card-back-footer">
<button class="button button-secondary flip-to-front">取消</button>
<button class="button button-primary download-btn" data-card="0" disabled>
<i class="fas fa-download"></i>
下载
</button>
</div>
</div>
</div>
</div>
<!-- 卡片2:视频去水印 -->
<div class="card-container" data-index="1">
<div class="card" id="card-1">
<!-- 卡片正面 -->
<div class="card-face card-front" style="background: linear-gradient(to bottom, #ef4444, #b91c1c);">
<div class="card-header">
<div class="icon-container">
<i class="fas fa-video"></i>
</div>
<div class="settings-icon flip-to-back">
<i class="fas fa-cog"></i>
</div>
</div>
<div class="card-title">
<h2>视频去水印</h2>
<h3>清除视频水印</h3>
</div>
<div class="card-image">
<div class="image-container" style="background-image: url('https://images.unsplash.com/photo-1536240478700-b869070f9279?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2760&q=80');"></div>
</div>
<div class="card-footer">
<div class="tag">
<i class="fas fa-video"></i>
<span>视频处理</span>
</div>
<p class="card-description">从视频中移除水印、标志和文字,让您的视频更加干净清晰。</p>
</div>
</div>
<!-- 卡片背面 -->
<div class="card-face card-back">
<div class="card-back-header">
<h2>视频去水印设置</h2>
<div class="close-icon flip-to-front">
<i class="fas fa-times"></i>
</div>
</div>
<div class="card-back-content">
<div class="settings-section">
<h3>功能开发中</h3>
<p style="text-align: center; padding: 2rem 0;">此功能正在开发中,敬请期待!</p>
</div>
</div>
<div class="card-back-footer">
<button class="button button-secondary flip-to-front">返回</button>
</div>
</div>
</div>
</div>
<!-- 卡片3:二维码生成 -->
<div class="card-container" data-index="2">
<div class="card" id="card-2">
<!-- 卡片正面 -->
<div class="card-face card-front" style="background: linear-gradient(to bottom, #10b981, #047857);">
<div class="card-header">
<div class="icon-container">
<i class="fas fa-qrcode"></i>
</div>
<div class="settings-icon flip-to-back">
<i class="fas fa-cog"></i>
</div>
</div>
<div class="card-title">
<h2>二维码生成</h2>
<h3>自定义二维码</h3>
</div>
<div class="card-image">
<div class="image-container" style="background-image: url('https://images.unsplash.com/photo-1622836829543-fd98500960a9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2760&q=80');"></div>
</div>
<div class="card-footer">
<div class="tag">
<i class="fas fa-qrcode"></i>
<span>二维码</span>
</div>
<p class="card-description">生成可自定义大小和颜色的二维码,适用于网址、文本和联系信息。</p>
</div>
</div>
<!-- 卡片背面 -->
<div class="card-face card-back">
<div class="card-back-header">
<h2>二维码生成设置</h2>
<div class="close-icon flip-to-front">
<i class="fas fa-times"></i>
</div>
</div>
<div class="card-back-content">
<div class="settings-section">
<h3>功能开发中</h3>
<p style="text-align: center; padding: 2rem 0;">此功能正在开发中,敬请期待!</p>
</div>
</div>
<div class="card-back-footer">
<button class="button button-secondary flip-to-front">返回</button>
</div>
</div>
</div>
</div>
</div>
<!-- 滑动指示器 -->
<div class="swipe-indicator">
<i class="fas fa-chevron-up"></i>
</div>
</div>
<!-- 导航指示器 -->
<div class="navigation-indicators" id="navigationIndicators">
<div class="indicator active" data-index="0"></div>
<div class="indicator" data-index="1"></div>
<div class="indicator" data-index="2"></div>
</div>
</div>
<!-- 隐藏的画布用于处理图像 -->
<canvas id="processingCanvas" style="display: none;"></canvas>
<!-- 通知 -->
<div class="notification" id="notification"></div>
<script>
// 卡片数据
const cardData = [
{
id: 0,
title: "图片格式转换",
originalImage: null,
originalWidth: 0,
originalHeight: 0,
originalAspectRatio: 1,
currentCompressionPreset: 'balanced'
},
{
id: 1,
title: "视频去水印"
},
{
id: 2,
title: "二维码生成"
}
];
// DOM元素
const cardStack = document.getElementById('cardStack');
const cardContainers = document.querySelectorAll('.card-container');
const cards = document.querySelectorAll('.card');
const navigationIndicators = document.getElementById('navigationIndicators');
const indicators = document.querySelectorAll('.indicator');
const processingCanvas = document.getElementById('processingCanvas');
const notification = document.getElementById('notification');
// 全局变量
let activeCardIndex = 0;
let startY = 0;
let isDragging = false;
let dragThreshold = 50; // 拖动阈值
let touchStartTime = 0;
let touchEndTime = 0;
// 初始化
document.addEventListener('DOMContentLoaded', function() {
// 设置初始卡片状态
updateCardPositions();
// 设置事件监听器
setupEventListeners();
});
// 设置事件监听器
function setupEventListeners() {
// 卡片堆叠滑动
cardStack.addEventListener('mousedown', handleDragStart);
cardStack.addEventListener('mousemove', handleDragMove);
cardStack.addEventListener('mouseup', handleDragEnd);
cardStack.addEventListener('mouseleave', handleDragEnd);
cardStack.addEventListener('touchstart', handleDragStart, { passive: true });
cardStack.addEventListener('touchmove', handleDragMove, { passive: false });
cardStack.addEventListener('touchend', handleDragEnd);
// 导航指示器点击
indicators.forEach(indicator => {
indicator.addEventListener('click', function() {
const index = parseInt(this.getAttribute('data-index'));
navigateToCard(index);
});
});
// 卡片翻转
document.querySelectorAll('.flip-to-back').forEach(btn => {
btn.addEventListener('click', function() {
const cardId = this.closest('.card').id;
document.getElementById(cardId).classList.add('flipped');
});
});
document.querySelectorAll('.flip-to-front').forEach(btn => {
btn.addEventListener('click', function() {
const cardId = this.closest('.card').id;
document.getElementById(cardId).classList.remove('flipped');
});
});
// 图片处理功能 - 为第一张卡片设置
setupImageProcessingEvents();
}
// 设置图片处理事件
function setupImageProcessingEvents() {
// 上传区域
document.querySelectorAll('.upload-area').forEach(area => {
const cardIndex = area.getAttribute('data-card');
const fileInput = area.querySelector('.file-input');
area.addEventListener('click', function() {
fileInput.click();
});
area.addEventListener('dragover', function(e) {
e.preventDefault();
this.style.backgroundColor = '#f1f5f9';
});
area.addEventListener('dragleave', function() {
this.style.backgroundColor = '';
});
area.addEventListener('drop', function(e) {
e.preventDefault();
this.style.backgroundColor = '';
if (e.dataTransfer.files.length) {
handleFileUpload(e.dataTransfer.files[0], cardIndex);
}
});
// 文件输入
fileInput.addEventListener('change', function() {
if (this.files.length) {
handleFileUpload(this.files[0], cardIndex);
}
});
});
// 格式选择
document.querySelectorAll('.format-select').forEach(select => {
const cardIndex = select.getAttribute('data-card');
select.addEventListener('change', function() {
updateFormatUI(cardIndex);
updateSizeEstimate(cardIndex);
});
});
// 质量范围
document.querySelectorAll('.quality-range').forEach(range => {
const cardIndex = range.closest('.quality-container').getAttribute('data-card');
const qualityValue = range.closest('.quality-container').querySelector('.quality-value');
range.addEventListener('input', function() {
qualityValue.textContent = `${this.value}%`;
updateSizeEstimate(cardIndex);
});
});
// 尺寸输入
document.querySelectorAll('.width-input').forEach(input => {
const cardIndex = input.getAttribute('data-card');
input.addEventListener('input', function() {
const lockRatio = document.querySelector(`.lock-ratio-checkbox[data-card="${cardIndex}"]`).checked;
const heightInput = document.querySelector(`.height-input[data-card="${cardIndex}"]`);
if (lockRatio && this.value && cardData[cardIndex].originalAspectRatio) {
const width = parseInt(this.value);
const height = Math.round(width / cardData[cardIndex].originalAspectRatio);
heightInput.value = height;
}
updateSizeEstimate(cardIndex);
});
});
document.querySelectorAll('.height-input').forEach(input => {
const cardIndex = input.getAttribute('data-card');
input.addEventListener('input', function() {
const lockRatio = document.querySelector(`.lock-ratio-checkbox[data-card="${cardIndex}"]`).checked;
const widthInput = document.querySelector(`.width-input[data-card="${cardIndex}"]`);
if (lockRatio && this.value && cardData[cardIndex].originalAspectRatio) {
const height = parseInt(this.value);
const width = Math.round(height * cardData[cardIndex].originalAspectRatio);
widthInput.value = width;
}
updateSizeEstimate(cardIndex);
});
});
// 锁定比例
document.querySelectorAll('.lock-ratio-checkbox').forEach(checkbox => {
const cardIndex = checkbox.getAttribute('data-card');
checkbox.addEventListener('change', function() {
if (this.checked) {
const widthInput = document.querySelector(`.width-input[data-card="${cardIndex}"]`);
const heightInput = document.querySelector(`.height-input[data-card="${cardIndex}"]`);
if (widthInput.value && cardData[cardIndex].originalAspectRatio) {
const width = parseInt(widthInput.value);
const height = Math.round(width / cardData[cardIndex].originalAspectRatio);
heightInput.value = height;
}
}
});
});
// 纵横比选择
document.querySelectorAll('.aspect-ratio-select').forEach(select => {
const cardIndex = select.getAttribute('data-card');
select.addEventListener('change', function() {
handleAspectRatioChange(cardIndex);
});
});
// 亮度范围
document.querySelectorAll('.brightness-range').forEach(range => {
const cardIndex = range.getAttribute('data-card');
const brightnessValue = range.closest('.filter-control').querySelector('.brightness-value');
range.addEventListener('input', function() {
brightnessValue.textContent = `${this.value}%`;
});
});
// 对比度范围
document.querySelectorAll('.contrast-range').forEach(range => {
const cardIndex = range.getAttribute('data-card');
const contrastValue = range.closest('.filter-control').querySelector('.contrast-value');
range.addEventListener('input', function() {
contrastValue.textContent = `${this.value}%`;
});
});
// 饱和度范围
document.querySelectorAll('.saturation-range').forEach(range => {
const cardIndex = range.getAttribute('data-card');
const saturationValue = range.closest('.filter-control').querySelector('.saturation-value');
range.addEventListener('input', function() {
saturationValue.textContent = `${this.value}%`;
});
});
// 压缩预设
document.querySelectorAll('.compression-presets').forEach(presetContainer => {
const cardIndex = presetContainer.getAttribute('data-card');
const presets = presetContainer.querySelectorAll('.compression-preset');
presets.forEach(preset => {
preset.addEventListener('click', function() {
presets.forEach(p => p.classList.remove('active'));
this.classList.add('active');
cardData[cardIndex].currentCompressionPreset = this.getAttribute('data-preset');
// 设置质量值
const qualityRange = document.querySelector(`.quality-range[data-card="${cardIndex}"]`);
const qualityValue = qualityRange.closest('.quality-container').querySelector('.quality-value');
switch (cardData[cardIndex].currentCompressionPreset) {
case 'high-quality':
qualityRange.value = 95;
break;
case 'balanced':
qualityRange.value = 80;
break;
case 'small-size':
qualityRange.value = 60;
break;
}
qualityValue.textContent = `${qualityRange.value}%`;
updateSizeEstimate(cardIndex);
});
});
});
// 下载按钮
document.querySelectorAll('.download-btn').forEach(btn => {
const cardIndex = btn.getAttribute('data-card');
btn.addEventListener('click', function() {
if (!cardData[cardIndex].originalImage) return;
processAndDownloadImage(cardIndex);
});
});
}
// 处理拖动开始
function handleDragStart(e) {
isDragging = true;
if (e.type === 'touchstart') {
startY = e.touches[0].clientY;
touchStartTime = new Date().getTime();
} else {
startY = e.clientY;
touchStartTime = new Date().getTime();
}
}
// 处理拖动移动
function handleDragMove(e) {
if (!isDragging) return;
let currentY;
if (e.type === 'touchmove') {
currentY = e.touches[0].clientY;
e.preventDefault(); // 防止页面滚动
} else {
currentY = e.clientY;
}
const deltaY = currentY - startY;
// 如果拖动距离超过阈值,防止页面滚动
if (Math.abs(deltaY) > 10) {
e.preventDefault();
}
}
// 处理拖动结束
function handleDragEnd(e) {
if (!isDragging) return;
let endY;
if (e.type === 'touchend') {
endY = e.changedTouches[0].clientY;
} else {
endY = e.clientY;
}
touchEndTime = new Date().getTime();
const deltaY = endY - startY;
const deltaTime = touchEndTime - touchStartTime;
const speed = Math.abs(deltaY) / deltaTime;
// 如果拖动速度快或距离大,切换卡片
if (speed > 0.5 || Math.abs(deltaY) > dragThreshold) {
if (deltaY > 0) {
// 向下滑动,显示上一张卡片
navigateToPrevCard();
} else {
// 向上滑动,显示下一张卡片
navigateToNextCard();
}
}
isDragging = false;
}
// 导航到下一张卡片
function navigateToNextCard() {
if (activeCardIndex < cardContainers.length - 1) {
navigateToCard(activeCardIndex + 1);
}
}
// 导航到上一张卡片
function navigateToPrevCard() {
if (activeCardIndex > 0) {
navigateToCard(activeCardIndex - 1);
}
}
// 导航到指定卡片
function navigateToCard(index) {
if (index < 0 || index >= cardContainers.length) return;
activeCardIndex = index;
updateCardPositions();
updateIndicators();
}
// 更新卡片位置
function updateCardPositions() {
cardContainers.forEach((container, index) => {
container.classList.remove('active', 'prev', 'next');
if (index === activeCardIndex) {
container.classList.add('active');
} else if (index === activeCardIndex - 1) {
container.classList.add('prev');
} else if (index === activeCardIndex + 1) {
container.classList.add('next');
}
});
}
// 更新指示器
function updateIndicators() {
indicators.forEach((indicator, index) => {
if (index === activeCardIndex) {
indicator.classList.add('active');
} else {
indicator.classList.remove('active');
}
});
}
// 处理文件上传
function handleFileUpload(file, cardIndex) {
if (!file.type.startsWith('image/')) {
showNotification('请选择有效的图片文件', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
// 保存原始图像
cardData[cardIndex].originalImage = img;
cardData[cardIndex].originalWidth = img.width;
cardData[cardIndex].originalHeight = img.height;
cardData[cardIndex].originalAspectRatio = img.width / img.height;
// 更新预览
const previewContainer = document.querySelector(`.preview-container[data-card="${cardIndex}"]`);
const previewImage = previewContainer.querySelector('.preview-image');
previewImage.src = e.target.result;
previewContainer.style.display = 'block';
// 更新尺寸输入
const widthInput = document.querySelector(`.width-input[data-card="${cardIndex}"]`);
const heightInput = document.querySelector(`.height-input[data-card="${cardIndex}"]`);
widthInput.value = img.width;
heightInput.value = img.height;
// 启用下载按钮
const downloadBtn = document.querySelector(`.download-btn[data-card="${cardIndex}"]`);
downloadBtn.disabled = false;
// 更新大小估计
updateSizeEstimate(cardIndex);
};
img.src = e.target.result;
};
reader.onerror = function() {
showNotification('读取文件时出错', 'error');
};
reader.readAsDataURL(file);
}
// 更新格式相关UI
function updateFormatUI(cardIndex) {
const formatSelect = document.querySelector(`.format-select[data-card="${cardIndex}"]`);
const qualityContainer = document.querySelector(`.quality-container[data-card="${cardIndex}"]`);
const format = formatSelect.value;
if (format === 'jpeg' || format === 'webp') {
qualityContainer.style.display = 'block';
} else {
qualityContainer.style.display = 'none';
}
}
// 处理纵横比变化
function handleAspectRatioChange(cardIndex) {
const aspectRatioSelect = document.querySelector(`.aspect-ratio-select[data-card="${cardIndex}"]`);
const widthInput = document.querySelector(`.width-input[data-card="${cardIndex}"]`);
const heightInput = document.querySelector(`.height-input[data-card="${cardIndex}"]`);
const lockRatioCheckbox = document.querySelector(`.lock-ratio-checkbox[data-card="${cardIndex}"]`);
const value = aspectRatioSelect.value;
if (value === 'original' && cardData[cardIndex].originalImage) {
widthInput.value = cardData[cardIndex].originalWidth;
heightInput.value = cardData[cardIndex].originalHeight;
lockRatioCheckbox.checked = true;
return;
}
if (value === 'free') {
lockRatioCheckbox.checked = false;
return;
}
lockRatioCheckbox.checked = true;
if (!widthInput.value || !cardData[cardIndex].originalImage) return;
let ratio;
switch (value) {
case '1:1':
ratio = 1;
break;
case '4:3':
ratio = 4/3;
break;
case '16:9':
ratio = 16/9;
break;
case '9:16':
ratio = 9/16;
break;
default:
ratio = cardData[cardIndex].originalAspectRatio;
}
const width = parseInt(widthInput.value);
const height = Math.round(width / ratio);
heightInput.value = height;
updateSizeEstimate(cardIndex);
}
// 更新大小估计
function updateSizeEstimate(cardIndex) {
if (!cardData[cardIndex].originalImage) return;
// 获取设置
const formatSelect = document.querySelector(`.format-select[data-card="${cardIndex}"]`);
const qualityRange = document.querySelector(`.quality-range[data-card="${cardIndex}"]`);
const widthInput = document.querySelector(`.width-input[data-card="${cardIndex}"]`);
const heightInput = document.querySelector(`.height-input[data-card="${cardIndex}"]`);
const sizeEstimate = document.querySelector(`.card-container[data-index="${cardIndex}"] .size-estimate`);
const sizeSaving = document.querySelector(`.card-container[data-index="${cardIndex}"] .size-saving`);
const format = formatSelect.value;
const quality = parseInt(qualityRange.value) / 100;
const width = widthInput.value ? parseInt(widthInput.value) : cardData[cardIndex].originalWidth;
const height = heightInput.value ? parseInt(heightInput.value) : cardData[cardIndex].originalHeight;
// 估算原始大小 (基于图像尺寸和颜色深度)
const originalSize = cardData[cardIndex].originalWidth * cardData[cardIndex].originalHeight * 4; // 假设每像素4字节 (RGBA)
// 估算输出大小
let estimatedSize;
if (format === 'jpeg') {
estimatedSize = width * height * 4 * quality * 0.25; // JPEG压缩率约为25%
} else if (format === 'png') {
estimatedSize = width * height * 4 * 0.8; // PNG压缩率约为80%
} else if (format === 'webp') {
estimatedSize = width * height * 4 * quality * 0.2; // WebP压缩率约为20%
} else {
estimatedSize = width * height * 4 * 0.7; // GIF压缩率约为70%
}
// 更新UI
sizeEstimate.textContent = formatFileSize(estimatedSize);
// 计算节省空间
const saving = originalSize - estimatedSize;
const savingPercentage = Math.round((saving / originalSize) * 100);
if (saving > 0) {
sizeSaving.textContent = `${formatFileSize(saving)} (${savingPercentage}%)`;
sizeSaving.style.color = '#10b981'; // 绿色
} else {
sizeSaving.textContent = `增加 ${formatFileSize(Math.abs(saving))} (${Math.abs(savingPercentage)}%)`;
sizeSaving.style.color = '#ef4444'; // 红色
}
}
// 处理并下载图像
function processAndDownloadImage(cardIndex) {
if (!cardData[cardIndex].originalImage) return;
// 获取设置
const formatSelect = document.querySelector(`.format-select[data-card="${cardIndex}"]`);
const qualityRange = document.querySelector(`.quality-range[data-card="${cardIndex}"]`);
const widthInput = document.querySelector(`.width-input[data-card="${cardIndex}"]`);
const heightInput = document.querySelector(`.height-input[data-card="${cardIndex}"]`);
const brightnessRange = document.querySelector(`.brightness-range[data-card="${cardIndex}"]`);
const contrastRange = document.querySelector(`.contrast-range[data-card="${cardIndex}"]`);
const saturationRange = document.querySelector(`.saturation-range[data-card="${cardIndex}"]`);
const format = formatSelect.value;
const quality = parseInt(qualityRange.value) / 100;
const width = widthInput.value ? parseInt(widthInput.value) : cardData[cardIndex].originalWidth;
const height = heightInput.value ? parseInt(heightInput.value) : cardData[cardIndex].originalHeight;
const brightness = 1 + parseInt(brightnessRange.value) / 100;
const contrast = 1 + parseInt(contrastRange.value) / 100;
const saturation = 1 + parseInt(saturationRange.value) / 100;
// 设置画布大小
processingCanvas.width = width;
processingCanvas.height = height;
// 获取上下文
const ctx = processingCanvas.getContext('2d');
// 绘制图像
ctx.drawImage(cardData[cardIndex].originalImage, 0, 0, width, height);
// 应用滤镜
if (brightness !== 1 || contrast !== 1 || saturation !== 1) {
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// 亮度
data[i] = data[i] * brightness;
data[i + 1] = data[i + 1] * brightness;
data[i + 2] = data[i + 2] * brightness;
// 对比度
data[i] = (data[i] - 128) * contrast + 128;
data[i + 1] = (data[i + 1] - 128) * contrast + 128;
data[i + 2] = (data[i + 2] - 128) * contrast + 128;
// 饱和度
if (saturation !== 1) {
const gray = 0.2989 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
data[i] = gray * (1 - saturation) + data[i] * saturation;
data[i + 1] = gray * (1 - saturation) + data[i + 1] * saturation;
data[i + 2] = gray * (1 - saturation) + data[i + 2] * saturation;
}
}
ctx.putImageData(imageData, 0, 0);
}
// 导出图像
let dataUrl;
if (format === 'jpeg' || format === 'webp') {
dataUrl = processingCanvas.toDataURL(`image/${format}`, quality);
} else {
dataUrl = processingCanvas.toDataURL(`image/${format}`);
}
// 创建下载链接
const link = document.createElement('a');
link.href = dataUrl;
link.download = `processed_image.${format}`;
link.click();
// 显示通知
showNotification('图片已成功下载', 'success');
}
// 显示通知
function showNotification(message, type = 'info') {
notification.textContent = message;
notification.className = `notification ${type}`;
notification.classList.add('show');
setTimeout(function() {
notification.classList.remove('show');
}, 3000);
}
// 格式化文件大小
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];
}
</script>
</body>
</html>
index.html
style.css
index.js