Files
InfoGenie/InfoGenie-frontend/public/toolbox/图片处理/图片转webp格式/index.html
2026-03-28 20:59:52 +08:00

332 lines
15 KiB
HTML
Raw 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>图片转 WebP</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
body { font-family: 'Inter', system-ui, sans-serif; }
.drop-zone {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.drop-zone.dragover {
background-color: rgb(249 250 251);
border-color: rgb(59 130 246);
}
.preview-img {
max-height: 320px;
object-fit: contain;
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
</style>
</head>
<body class="bg-zinc-950 text-zinc-100 min-h-screen">
<div class="max-w-5xl mx-auto px-4 py-8">
<!-- 头部 -->
<div class="flex flex-col items-center mb-10">
<div class="flex items-center gap-3 mb-2">
<div class="w-9 h-9 bg-blue-600 rounded-xl flex items-center justify-center text-white font-bold text-2xl">W</div>
<h1 class="text-4xl font-semibold tracking-tight">图片转 WebP</h1>
</div>
<p class="text-zinc-400 text-center max-w-md">
本地转换 · 零上传 · 隐私安全<br>
<span class="text-xs">支持 JPEG / PNG / GIF / BMP / AVIF 等任意格式</span>
</p>
</div>
<div id="main" class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- 左侧:上传 & 原图 -->
<div class="space-y-6">
<!-- 上传区 -->
<div id="upload-zone"
class="drop-zone border-2 border-dashed border-zinc-700 hover:border-blue-500 rounded-3xl h-80 flex flex-col items-center justify-center cursor-pointer bg-zinc-900/50">
<input type="file" id="file-input" accept="image/*" class="hidden">
<div class="text-center">
<div class="w-16 h-16 mx-auto mb-4 bg-zinc-800 rounded-2xl flex items-center justify-center">
📸
</div>
<p class="text-lg font-medium mb-1">拖拽图片到这里</p>
<p class="text-sm text-zinc-400">或点击选择文件</p>
<p class="text-[10px] text-zinc-500 mt-6">支持所有常见图片格式</p>
</div>
</div>
<!-- 原图预览 -->
<div id="original-preview" class="hidden bg-zinc-900 rounded-3xl overflow-hidden border border-zinc-800">
<div class="bg-zinc-950 px-5 py-3 flex items-center justify-between border-b border-zinc-800">
<span class="font-medium text-sm">原图</span>
<span id="original-info" class="text-xs text-zinc-400"></span>
</div>
<div class="p-5">
<img id="original-img" class="preview-img w-full mx-auto rounded-2xl" alt="原图">
</div>
</div>
</div>
<!-- 右侧:设置 & 转换结果 -->
<div class="space-y-6">
<!-- 参数设置 -->
<div id="controls" class="hidden bg-zinc-900 rounded-3xl p-6 border border-zinc-800">
<div class="flex items-center justify-between mb-4">
<span class="font-medium text-sm">转换设置</span>
</div>
<div class="space-y-6">
<!-- 质量滑块 -->
<div>
<div class="flex justify-between text-sm mb-2">
<span class="text-zinc-400">压缩质量</span>
<span id="quality-value" class="font-mono text-blue-400">82</span>
</div>
<input type="range" id="quality"
min="1" max="100" value="82"
class="w-full accent-blue-500">
<div class="flex justify-between text-[10px] text-zinc-500 mt-1">
<span>节省空间</span>
<span>最高画质</span>
</div>
</div>
<!-- 转换按钮 -->
<button id="convert-btn"
class="w-full bg-blue-600 hover:bg-blue-500 transition-colors text-white font-medium py-4 rounded-2xl text-lg shadow-lg shadow-blue-500/30 flex items-center justify-center gap-2">
<span>🚀</span>
<span>开始转换为 WebP</span>
</button>
</div>
</div>
<!-- 转换结果 -->
<div id="result" class="hidden bg-zinc-900 rounded-3xl overflow-hidden border border-zinc-800">
<div class="bg-zinc-950 px-5 py-3 flex items-center justify-between border-b border-zinc-800">
<span class="font-medium text-sm">转换结果</span>
<button id="convert-again"
class="text-xs text-blue-400 hover:text-blue-300 flex items-center gap-1">
<span></span> 转换另一张
</button>
</div>
<div class="p-5">
<img id="webp-img" class="preview-img w-full mx-auto rounded-2xl" alt="WebP">
</div>
<!-- 数据对比 -->
<div class="grid grid-cols-2 gap-px bg-zinc-800 mx-5 mb-5 rounded-2xl overflow-hidden">
<div class="bg-zinc-900 p-4">
<div class="text-xs text-zinc-400">原图大小</div>
<div id="orig-size" class="font-mono text-lg font-medium text-white"></div>
<div id="orig-dim" class="text-xs text-zinc-500 mt-1"></div>
</div>
<div class="bg-zinc-900 p-4">
<div class="text-xs text-emerald-400">WebP 大小</div>
<div id="webp-size" class="font-mono text-lg font-medium text-emerald-400"></div>
<div id="webp-dim" class="text-xs text-zinc-500 mt-1"></div>
</div>
</div>
<div class="mx-5 mb-6 bg-emerald-950 border border-emerald-900 rounded-2xl px-4 py-3 flex items-center justify-between">
<span class="text-emerald-400 text-sm">压缩率</span>
<span id="compress-rate" class="font-mono text-2xl font-semibold text-emerald-400"></span>
</div>
<!-- 下载按钮 -->
<div class="px-5 pb-5">
<a id="download-link"
class="block text-center bg-white text-zinc-950 font-medium py-4 rounded-2xl text-lg hover:bg-zinc-100 transition-colors">
⬇️ 下载 WebP 文件
</a>
</div>
</div>
</div>
</div>
<!-- 提示 -->
<div class="mt-12 text-center text-xs text-zinc-500">
全部在浏览器本地完成 · 无需联网 · 图片永不上传
</div>
</div>
<script>
// Tailwind 脚本已通过 CDN 加载,这里无需额外 script
function initTailwind() {
// 已在 head 加载
}
let originalFile = null;
let webpBlob = null;
let originalUrl = null;
let webpUrl = null;
const uploadZone = document.getElementById('upload-zone');
const fileInput = document.getElementById('file-input');
const originalPreview = document.getElementById('original-preview');
const originalImg = document.getElementById('original-img');
const originalInfo = document.getElementById('original-info');
const controls = document.getElementById('controls');
const qualitySlider = document.getElementById('quality');
const qualityValue = document.getElementById('quality-value');
const convertBtn = document.getElementById('convert-btn');
const resultPanel = document.getElementById('result');
const webpImg = document.getElementById('webp-img');
const origSizeEl = document.getElementById('orig-size');
const origDimEl = document.getElementById('orig-dim');
const webpSizeEl = document.getElementById('webp-size');
const webpDimEl = document.getElementById('webp-dim');
const compressRateEl = document.getElementById('compress-rate');
const downloadLink = document.getElementById('download-link');
const convertAgain = document.getElementById('convert-again');
// 拖拽事件
function setupDragDrop() {
uploadZone.addEventListener('dragover', e => {
e.preventDefault();
uploadZone.classList.add('dragover');
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.classList.remove('dragover');
});
uploadZone.addEventListener('drop', e => {
e.preventDefault();
uploadZone.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
uploadZone.addEventListener('click', () => fileInput.click());
}
// 文件选择
fileInput.addEventListener('change', e => {
if (e.target.files.length > 0) handleFiles(e.target.files);
});
function handleFiles(files) {
const file = files[0];
if (!file || !file.type.startsWith('image/')) {
alert('请选择图片文件!');
return;
}
originalFile = file;
// 释放之前的 URL
if (originalUrl) URL.revokeObjectURL(originalUrl);
originalUrl = URL.createObjectURL(file);
originalImg.src = originalUrl;
// 显示原图信息
const sizeMB = (file.size / 1024 / 1024).toFixed(2);
originalInfo.textContent = `${file.name} · ${(file.size / 1024).toFixed(1)} KB`;
originalPreview.classList.remove('hidden');
controls.classList.remove('hidden');
resultPanel.classList.add('hidden');
// 重置
webpBlob = null;
}
// 质量滑块实时显示
qualitySlider.addEventListener('input', () => {
qualityValue.textContent = qualitySlider.value;
});
// 转换核心函数
async function convertToWebP() {
if (!originalFile) return;
convertBtn.innerHTML = `
<svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
`;
convertBtn.disabled = true;
const img = new Image();
img.onload = async () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const quality = parseFloat(qualitySlider.value) / 100;
// 使用 toBlob 获得更精确的 Blob
canvas.toBlob(async (blob) => {
webpBlob = blob;
if (webpUrl) URL.revokeObjectURL(webpUrl);
webpUrl = URL.createObjectURL(webpBlob);
webpImg.src = webpUrl;
// 计算数据
const origSize = originalFile.size;
const webpSize = webpBlob.size;
const rate = ((origSize - webpSize) / origSize * 100).toFixed(1);
// 填充界面
origSizeEl.textContent = formatSize(origSize);
origDimEl.textContent = `${img.width}×${img.height}`;
webpSizeEl.textContent = formatSize(webpSize);
webpDimEl.textContent = `${img.width}×${img.height}`;
compressRateEl.textContent = rate + '%';
// 下载链接
const newName = originalFile.name.replace(/\.[^/.]+$/, "") + ".webp";
downloadLink.download = newName;
downloadLink.href = webpUrl;
// 显示结果
resultPanel.classList.remove('hidden');
convertBtn.innerHTML = `✅ 已完成`;
setTimeout(() => {
convertBtn.innerHTML = `<span>🚀</span><span>重新转换</span>`;
convertBtn.disabled = false;
}, 800);
}, 'image/webp', quality);
};
img.src = originalUrl;
}
function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
else return (bytes / 1024 / 1024).toFixed(2) + ' MB';
}
// 绑定按钮
convertBtn.addEventListener('click', convertToWebP);
convertAgain.addEventListener('click', () => {
resultPanel.classList.add('hidden');
// 清空结果但保留原图
});
// 初始化
function init() {
setupDragDrop();
// 初始质量显示
qualityValue.textContent = qualitySlider.value;
// 键盘快捷键:回车转换
document.addEventListener('keydown', e => {
if (e.key === 'Enter' && !resultPanel.classList.contains('hidden')) {
convertToWebP();
}
});
}
window.onload = init;
</script>
</body>
</html>