Files
InfoGenie/InfoGenie-frontend/public/toolbox/图片处理/png和jpg格式互转/png和jpg相互转化.html
2026-04-01 22:03:57 +08:00

343 lines
16 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>PNG ↔ JPG 图片转换器</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap');
body { font-family: 'Noto Sans SC
', system-ui, sans-serif; }
.drop-zone {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.drop-zone.dragover {
border-color: #3b82f6;
background-color: #eff6ff;
transform: scale(1.02);
}
canvas { display: none; }
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-indigo-50 min-h-screen py-12">
<div class="max-w-6xl mx-auto px-6">
<!-- 头部 -->
<div class="text-center mb-10">
<div class="inline-flex items-center gap-3 bg-white shadow-md rounded-3xl px-8 py-4">
<div class="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl flex items-center justify-center text-white text-3xl">🖼️</div>
<h1 class="text-4xl font-bold text-gray-900 tracking-tight">PNG ↔ JPG 转换器</h1>
</div>
<p class="mt-4 text-gray-600 max-w-md mx-auto">纯浏览器本地转换 • 零上传 • 安全隐私 • 秒级处理</p>
</div>
<div class="bg-white rounded-3xl shadow-2xl overflow-hidden">
<div class="p-8">
<!-- 上传区域 -->
<div id="drop-zone"
class="drop-zone border-4 border-dashed border-gray-300 rounded-3xl p-16 text-center cursor-pointer hover:border-blue-500">
<input type="file" id="file-input" accept="image/png,image/jpeg" class="hidden">
<div class="mx-auto w-20 h-20 bg-blue-100 rounded-full flex items-center justify-center mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v-4m0 0l4 4m-4-4l4-4m12 4v4m0 0l-4-4m4 4l-4 4" />
</svg>
</div>
<p class="text-2xl font-semibold text-gray-700">点击或拖拽图片到此区域</p>
<p class="text-sm text-gray-500 mt-3">支持 PNG / JPG 格式,最大 50MB</p>
</div>
<!-- 主内容区 -->
<div id="main-content" class="hidden mt-12 grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- 原图 -->
<div class="lg:col-span-5 bg-gray-50 rounded-3xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-lg text-gray-800 flex items-center gap-2">
<span class="w-3 h-3 bg-green-500 rounded-full"></span>
原图
</h3>
<span id="original-info" class="text-xs text-gray-500 font-mono"></span>
</div>
<div class="aspect-video bg-white rounded-2xl overflow-hidden border border-gray-200 flex items-center justify-center">
<img id="original-img" class="max-h-full max-w-full object-contain" alt="原图预览">
</div>
</div>
<!-- 控制面板 -->
<div class="lg:col-span-2 flex flex-col justify-center gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">转换目标格式</label>
<div class="flex gap-2">
<button id="btn-jpg"
class="flex-1 py-3 px-6 rounded-2xl font-medium transition-all active:scale-95 border-2 border-transparent bg-white shadow-sm hover:shadow-md data-[active=true]:border-blue-600 data-[active=true]:bg-blue-50">
转为 JPG
</button>
<button id="btn-png"
class="flex-1 py-3 px-6 rounded-2xl font-medium transition-all active:scale-95 border-2 border-transparent bg-white shadow-sm hover:shadow-md data-[active=true]:border-blue-600 data-[active=true]:bg-blue-50">
转为 PNG
</button>
</div>
</div>
<!-- JPG 选项 -->
<div id="jpg-options" class="hidden space-y-5">
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="font-medium text-gray-700">JPG 质量</span>
<span id="quality-value" class="font-mono text-blue-600">92%</span>
</div>
<input type="range" id="quality-slider" min="10" max="100" value="92"
class="w-full accent-blue-600">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">透明背景填充颜色PNG→JPG</label>
<div class="flex gap-3 items-center">
<input type="color" id="bg-color" value="#ffffff"
class="w-12 h-10 rounded-xl border border-gray-300 cursor-pointer">
<div id="bg-color-hex" class="font-mono text-sm text-gray-500">#ffffff</div>
</div>
</div>
</div>
<button onclick="convertImage()"
class="mt-4 w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white font-semibold py-4 rounded-3xl shadow-lg transition-all active:scale-95 flex items-center justify-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7-7 7" />
</svg>
开始转换
</button>
</div>
<!-- 转换后 -->
<div class="lg:col-span-5 bg-gray-50 rounded-3xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-lg text-gray-800 flex items-center gap-2">
<span class="w-3 h-3 bg-purple-500 rounded-full"></span>
转换结果
</h3>
<span id="converted-info" class="text-xs text-gray-500 font-mono"></span>
</div>
<div class="aspect-video bg-white rounded-2xl overflow-hidden border border-gray-200 flex items-center justify-center relative">
<img id="converted-img" class="max-h-full max-w-full object-contain" alt="转换后预览">
<div id="converted-placeholder"
class="absolute inset-0 flex flex-col items-center justify-center text-gray-400">
<div class="w-16 h-16 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin mb-4"></div>
<p class="text-sm">转换后图片将显示在这里</p>
</div>
</div>
</div>
</div>
</div>
<!-- 下载栏 -->
<div id="download-bar" class="hidden border-t bg-gray-50 px-8 py-5 flex items-center justify-between">
<div class="text-sm text-gray-600">
文件已转换完成 • <span id="file-size" class="font-mono"></span>
</div>
<button onclick="downloadImage()"
class="bg-emerald-600 hover:bg-emerald-700 text-white px-8 py-3 rounded-2xl font-semibold flex items-center gap-3 transition-all active:scale-95">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v-4m0 0l4 4m-4-4l4-4m12 4v4m0 0l-4-4m4 4l-4 4" />
</svg>
下载转换后的图片
</button>
</div>
</div>
<div class="text-center mt-8 text-xs text-gray-400">
完全本地运行 • 无需联网 • 图片不会离开您的浏览器
</div>
</div>
<canvas id="canvas" class="hidden"></canvas>
<script>
// 全局变量
let originalFile = null;
let originalDataURL = null;
let convertedDataURL = null;
let outputFormat = 'image/jpeg';
let currentQuality = 0.92;
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d', { willReadFrequently: true });
// Tailwind 脚本已加载,无需额外初始化
// 拖拽与点击上传
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) handleFile(e.target.files[0]);
});
// 拖拽事件
['dragover', 'dragenter'].forEach(evt => {
dropZone.addEventListener(evt, (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
});
['dragleave', 'dragend'].forEach(evt => {
dropZone.addEventListener(evt, () => dropZone.classList.remove('dragover'));
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
handleFile(e.dataTransfer.files[0]);
}
});
function handleFile(file) {
if (!file.type.startsWith('image/') || !['image/png', 'image/jpeg'].includes(file.type)) {
alert('❌ 请上传 PNG 或 JPG 格式的图片!');
return;
}
if (file.size > 50 * 1024 * 1024) {
alert('❌ 文件过大!请上传小于 50MB 的图片。');
return;
}
originalFile = file;
const reader = new FileReader();
reader.onload = function(e) {
originalDataURL = e.target.result;
document.getElementById('original-img').src = originalDataURL;
// 显示信息
const img = new Image();
img.onload = () => {
const sizeKB = (file.size / 1024).toFixed(1);
document.getElementById('original-info').textContent =
`${img.width}×${img.height}${sizeKB} KB`;
};
img.src = originalDataURL;
// 显示主界面
document.getElementById('main-content').classList.remove('hidden');
document.getElementById('download-bar').classList.add('hidden');
document.getElementById('converted-img').src = '';
document.getElementById('converted-placeholder').style.display = 'flex';
};
reader.readAsDataURL(file);
}
// 格式切换
const btnJPG = document.getElementById('btn-jpg');
const btnPNG = document.getElementById('btn-png');
const jpgOptions = document.getElementById('jpg-options');
btnJPG.addEventListener('click', () => {
outputFormat = 'image/jpeg';
btnJPG.dataset.active = 'true';
btnPNG.dataset.active = 'false';
jpgOptions.classList.remove('hidden');
});
btnPNG.addEventListener('click', () => {
outputFormat = 'image/png';
btnPNG.dataset.active = 'true';
btnJPG.dataset.active = 'false';
jpgOptions.classList.add('hidden');
});
// 默认选择 JPG
btnJPG.click();
// 质量滑块
const qualitySlider = document.getElementById('quality-slider');
const qualityValue = document.getElementById('quality-value');
qualitySlider.addEventListener('input', () => {
currentQuality = qualitySlider.value / 100;
qualityValue.textContent = qualitySlider.value + '%';
});
// 背景颜色
const bgColorInput = document.getElementById('bg-color');
const bgColorHex = document.getElementById('bg-color-hex');
bgColorInput.addEventListener('input', () => {
bgColorHex.textContent = bgColorInput.value.toUpperCase();
});
// 转换函数
window.convertImage = function() {
if (!originalDataURL) {
alert('请先上传图片!');
return;
}
const loadingPlaceholder = document.getElementById('converted-placeholder');
loadingPlaceholder.style.display = 'none';
const img = new Image();
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (outputFormat === 'image/jpeg') {
// PNG→JPG 时填充背景色
ctx.fillStyle = bgColorInput.value;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 绘制图片
ctx.drawImage(img, 0, 0);
// 生成新 DataURL
convertedDataURL = canvas.toDataURL(outputFormat, currentQuality);
// 显示转换结果
document.getElementById('converted-img').src = convertedDataURL;
// 计算新大小
const newSize = Math.round((convertedDataURL.length * 3 / 4) / 1024);
document.getElementById('converted-info').textContent =
`${img.width}×${img.height}${newSize} KB`;
// 显示下载栏
document.getElementById('download-bar').classList.remove('hidden');
document.getElementById('file-size').textContent = `${newSize} KB`;
};
img.src = originalDataURL;
};
// 下载函数
window.downloadImage = function() {
if (!convertedDataURL) return;
const link = document.createElement('a');
link.href = convertedDataURL;
// 生成新文件名
let originalName = originalFile.name;
const ext = outputFormat === 'image/jpeg' ? '.jpg' : '.png';
const baseName = originalName.substring(0, originalName.lastIndexOf('.')) || originalName;
link.download = baseName + ext;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// 键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.key === '/' && document.getElementById('main-content').classList.contains('hidden')) {
fileInput.click();
}
});
</script>
</body>
</html>