update: 2026-03-28 20:59
This commit is contained in:
332
InfoGenie-frontend/public/toolbox/图片处理/图片转webp格式/index.html
Normal file
332
InfoGenie-frontend/public/toolbox/图片处理/图片转webp格式/index.html
Normal file
@@ -0,0 +1,332 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user