<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">图片翻转工具 · 水平垂直镜像</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #f5f7fb;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 16px;
}
.card {
background: white;
border-radius: 32px;
box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.15), 0 4px 18px rgba(0,0,0,0.05);
max-width: 760px;
width: 100%;
padding: 28px 24px 32px;
transition: all 0.2s ease;
}
h1 {
font-weight: 600;
font-size: 1.9rem;
letter-spacing: -0.01em;
color: #0b1a33;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 8px;
}
h1 small {
font-size: 0.9rem;
font-weight: 400;
color: #64748b;
margin-left: 6px;
}
.subhead {
color: #475569;
margin-bottom: 24px;
border-left: 4px solid #3b82f6;
padding-left: 14px;
background: #f8fafc;
border-radius: 0 8px 8px 0;
line-height: 1.4;
font-size: 0.95rem;
}
.upload-area {
background: #f1f5f9;
border: 2px dashed #cbd5e1;
border-radius: 24px;
padding: 32px 16px;
text-align: center;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
}
.upload-area:hover {
background: #eef2f6;
border-color: #94a3b8;
}
.upload-area.dragover {
background: #e2e8f0;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59,130,246,0.15);
}
.upload-icon {
font-size: 48px;
line-height: 1;
margin-bottom: 8px;
opacity: 0.7;
}
.upload-text {
font-weight: 500;
color: #1e293b;
}
.upload-hint {
font-size: 0.85rem;
color: #64748b;
margin-top: 8px;
}
#fileInput {
display: none;
}
.canvas-wrapper {
position: relative;
margin: 22px 0 18px;
border-radius: 20px;
overflow: hidden;
background: #f1f5f9;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.04);
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
#imageCanvas {
display: block;
max-width: 100%;
height: auto;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.04);
transition: transform 0.1s ease;
image-rendering: auto;
}
.placeholder-note {
color: #64748b;
padding: 48px 16px;
font-size: 0.95rem;
text-align: center;
width: 100%;
}
.control-bar {
display: flex;
flex-wrap: wrap;
gap: 14px 18px;
align-items: center;
justify-content: center;
margin: 20px 0 18px;
}
.btn-group {
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}
.btn {
background: white;
border: 1px solid #d1d9e6;
padding: 10px 20px;
border-radius: 40px;
font-weight: 500;
font-size: 0.95rem;
color: #1e293b;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.02);
transition: all 0.12s ease;
background: #f9fafc;
}
.btn:hover {
background: #f0f4fe;
border-color: #3b82f6;
color: #1e4b8c;
box-shadow: 0 4px 8px rgba(59,130,246,0.08);
transform: translateY(-1px);
}
.btn:active {
transform: scale(0.96);
background: #e6edf8;
}
.btn-primary {
background: #2563eb;
border-color: #2563eb;
color: white;
box-shadow: 0 4px 8px rgba(37,99,235,0.15);
}
.btn-primary:hover {
background: #1d4ed8;
border-color: #1d4ed8;
color: white;
box-shadow: 0 6px 12px rgba(37,99,235,0.25);
}
.btn-success {
background: #0f973d;
border-color: #0f973d;
color: white;
box-shadow: 0 4px 8px rgba(15,151,61,0.15);
}
.btn-success:hover {
background: #0b7e33;
border-color: #0b7e33;
color: white;
}
.btn-icon {
font-size: 1.2rem;
line-height: 1;
}
.badge {
background: #e9edf2;
border-radius: 30px;
padding: 2px 12px;
font-size: 0.8rem;
color: #2c3e50;
display: inline-block;
}
.status-row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
color: #334155;
margin: 8px 0 4px;
padding: 0 4px;
}
.footer-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
margin-top: 8px;
}
.reset-link {
color: #64748b;
background: none;
border: none;
font-size: 0.9rem;
cursor: pointer;
text-decoration: underline dotted;
padding: 4px 6px;
}
.reset-link:hover {
color: #1e293b;
}
@media (max-width: 480px) {
.card { padding: 20px 16px; }
.btn { padding: 8px 16px; font-size: 0.85rem; }
.btn-group { gap: 8px; }
}
</style>
</head>
<body>
<div class="card">
<h1>
🖼️ 翻转工具
<small>水平 · 垂直镜像</small>
</h1>
<div class="subhead">
上传图片,一键水平翻转 (左右镜像) 或垂直翻转 (上下镜像)
</div>
<!-- 上传区域 -->
<div class="upload-area" id="dropZone">
<div class="upload-icon">📁</div>
<div class="upload-text">点击选择图片 或 拖拽图片到此处</div>
<div class="upload-hint">支持 JPG / PNG / WebP 等常见格式</div>
<input type="file" id="fileInput" accept="image/*">
</div>
<!-- 画布预览区 -->
<div class="canvas-wrapper">
<canvas id="imageCanvas" width="600" height="400"></canvas>
<div class="placeholder-note" id="placeholderMsg">暂无图片 · 请上传</div>
</div>
<!-- 翻转控制 -->
<div class="control-bar">
<div class="btn-group">
<button class="btn" id="flipHBtn" title="水平镜像 (左右翻转)">
<span class="btn-icon">↔</span> 水平翻转
</button>
<button class="btn" id="flipVBtn" title="垂直镜像 (上下翻转)">
<span class="btn-icon">↕</span> 垂直翻转
</button>
</div>
</div>
<!-- 状态与下载 -->
<div class="status-row">
<span id="imageStatus"><span class="badge">⏳ 未加载</span></span>
<span id="flipInfo" style="font-weight: 450;">镜像: 无</span>
</div>
<div class="footer-actions">
<button class="btn btn-success" id="downloadBtn">
<span class="btn-icon">⬇</span> 下载翻转图片
</button>
<button class="reset-link" id="resetBtn">↺ 重置为原始方向</button>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
const fileInput = document.getElementById('fileInput');
const dropZone = document.getElementById('dropZone');
const placeholderMsg = document.getElementById('placeholderMsg');
const imageStatus = document.getElementById('imageStatus');
const flipInfo = document.getElementById('flipInfo');
const flipHBtn = document.getElementById('flipHBtn');
const flipVBtn = document.getElementById('flipVBtn');
const downloadBtn = document.getElementById('downloadBtn');
const resetBtn = document.getElementById('resetBtn');
// 状态存储
let sourceImage = null; // 原始图片对象 (HTMLImageElement)
let canvasWidth = 600;
let canvasHeight = 400;
let flipH = false; // 当前水平镜像状态
let flipV = false; // 当前垂直镜像状态
// 初始化显示占位
function drawPlaceholder() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
placeholderMsg.style.display = 'block';
canvas.style.background = '#f1f5f9';
imageStatus.innerHTML = `<span class="badge">⏳ 未加载</span>`;
flipInfo.textContent = '镜像: 无';
}
drawPlaceholder();
// 更新翻转状态显示
function updateFlipInfo() {
const parts = [];
if (flipH) parts.push('水平');
if (flipV) parts.push('垂直');
if (parts.length === 0) flipInfo.textContent = '镜像: 无';
else flipInfo.textContent = `镜像: ${parts.join(' + ')}`;
}
// 核心绘制: 根据当前翻转状态绘制图像
function renderImage() {
if (!sourceImage) {
drawPlaceholder();
return;
}
placeholderMsg.style.display = 'none';
canvas.style.background = '#fff';
// 计算 canvas 尺寸适应图片比例 (保持比例, 最大宽600)
let imgW = sourceImage.naturalWidth || sourceImage.width;
let imgH = sourceImage.naturalHeight || sourceImage.height;
if (imgW === 0 || imgH === 0) {
// 回退
imgW = 300; imgH = 200;
}
const maxW = 600;
let displayW, displayH;
if (imgW > maxW) {
displayW = maxW;
displayH = (imgH / imgW) * maxW;
} else {
displayW = imgW;
displayH = imgH;
}
// 确保整数 (避免模糊)
displayW = Math.floor(displayW);
displayH = Math.floor(displayH);
if (displayW < 20) displayW = 20;
if (displayH < 20) displayH = 20;
canvas.width = displayW;
canvas.height = displayH;
canvasWidth = displayW;
canvasHeight = displayH;
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ---------- 镜像变换 ----------
ctx.save();
// 水平翻转: 先平移再缩放 (-1, 1)
if (flipH && flipV) {
ctx.translate(canvas.width, canvas.height);
ctx.scale(-1, -1);
} else if (flipH) {
ctx.translate(canvas.width, 0);
ctx.scale(-1, 1);
} else if (flipV) {
ctx.translate(0, canvas.height);
ctx.scale(1, -1);
}
// 绘制图像 (使用原始图片大小)
ctx.drawImage(sourceImage, 0, 0, canvas.width, canvas.height);
ctx.restore();
// 更新状态UI
const label = sourceImage.alt || '图片';
imageStatus.innerHTML = `<span class="badge">✅ ${label} · ${canvas.width}×${canvas.height}</span>`;
updateFlipInfo();
}
// 从文件加载图片
function loadImageFile(file) {
if (!file || !file.type.startsWith('image/')) {
alert('请选择有效的图片文件');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
sourceImage = img;
// 重置翻转状态
flipH = false;
flipV = false;
renderImage();
};
img.onerror = () => {
alert('图片加载失败,请尝试其他文件');
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
// ----- 事件绑定 -----
// 点击上传
dropZone.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
if (e.target.files && e.target.files[0]) {
loadImageFile(e.target.files[0]);
}
// 重置input以允许重复上传相同文件
fileInput.value = '';
});
// 拖拽上传
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files && files.length > 0) {
loadImageFile(files[0]);
}
});
// 水平翻转
flipHBtn.addEventListener('click', () => {
if (!sourceImage) {
alert('请先上传图片');
return;
}
flipH = !flipH;
renderImage();
});
// 垂直翻转
flipVBtn.addEventListener('click', () => {
if (!sourceImage) {
alert('请先上传图片');
return;
}
flipV = !flipV;
renderImage();
});
// 重置 (清除所有翻转)
resetBtn.addEventListener('click', () => {
if (!sourceImage) {
return;
}
if (flipH === false && flipV === false) {
// 如果已经是原始状态,轻提示
return;
}
flipH = false;
flipV = false;
renderImage();
});
// 下载
downloadBtn.addEventListener('click', () => {
if (!sourceImage) {
alert('没有图片可下载,请先上传');
return;
}
// 导出当前canvas内容 (已应用翻转)
const link = document.createElement('a');
link.download = `flipped_${Date.now()}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
});
// 额外:键盘快捷键? (非必须)
// 保证在页面内也能重置样式
window.addEventListener('load', () => {
// 若canvas显示占位, 确保宽高
});
// 小优化:如果用户点击重置时无图片,忽略
// 扩展: 双击canvas重置? (个人喜好)
canvas.addEventListener('dblclick', () => {
if (sourceImage) {
flipH = false;
flipV = false;
renderImage();
}
});
})();
</script>
</body>
</html>
核心功能与操作流程
您可以通过上传图片,并点击按钮来应用镜像效果,其核心交互流程如下:

- 图片加载:您可以通过点击上传区域或直接拖拽图片来加载图片,支持 JPG、PNG 等常见格式。
- 应用翻转:点击 “水平翻转” 按钮可使图片左右镜像,点击 “垂直翻转” 按钮可使图片上下镜像,您可以组合使用这两个按钮。
- 管理与导出:界面会清晰显示当前应用的镜像状态,您可以通过 “重置为原始方向” 按钮撤销所有翻转,或使用 “下载翻转图片” 按钮保存处理后的图片。
优化建议: 您可以根据需要调整 maxW 变量(第166行附近)的值,以修改画布预览的最大宽度,从而适配不同的展示布局。
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。