Files
InfoGenie/InfoGenie-frontend/public/toolbox/图片转Base64编码/index.html
2025-12-13 20:53:50 +08:00

327 lines
13 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, viewport-fit=cover" />
<title>图片转化 base64 编码</title>
<meta name="description" content="将图片快速转换为 Base64 / Data URL支持一键复制与清空适配手机竖屏。" />
<style>
:root{
--bg1:#dff7d6; /* 淡绿色 */
--bg2:#eef8c9; /* 淡黄绿色 */
--card:#ffffffcc;
--text:#0f2f1a;
--muted:#3d5f46;
--accent:#6ebf75;
--accent-2:#a6d98d;
--danger:#b55252;
--radius:18px;
--shadow:0 10px 24px rgba(0,0,0,.08), 0 2px 8px rgba(0,0,0,.04);
}
html,body{height:100%;}
body{
margin:0;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
color:var(--text);
background: linear-gradient(135deg,var(--bg1),var(--bg2));
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
.container{
max-width: 860px;
padding: 16px clamp(14px,4vw,28px) 28px;
margin: 0 auto;
display:flex;
flex-direction:column;
gap:14px;
}
header{
text-align:center;
padding-top: 8px;
padding-bottom: 6px;
}
h1{
margin:0 0 6px;
font-size: clamp(20px, 5.5vw, 32px);
letter-spacing: .5px;
font-weight: 800;
background: linear-gradient(90deg, #3a7f43, #79c26f);
-webkit-background-clip: text;
background-clip:text;
color: transparent;
}
.subtitle{
font-size: clamp(12px, 3.6vw, 14px);
color: color-mix(in oklab, var(--muted) 80%, white);
}
.card{
background: var(--card);
border: 1px solid rgba(99, 135, 102, .15);
backdrop-filter: blur(6px);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 14px;
}
.uploader{
display:grid;
gap:12px;
}
.dropzone{
position:relative;
border:2px dashed rgba(59, 120, 74, .35);
border-radius: calc(var(--radius) - 6px);
padding: 18px;
background: linear-gradient(180deg, rgba(255,255,255,.85), rgba(255,255,255,.65));
transition: .2s ease;
cursor: pointer;
display:flex;
align-items:center;
gap:14px;
}
.dropzone:hover{ border-color: rgba(59,120,74,.6); }
.dropzone.dragover{ box-shadow: inset 0 0 0 3px rgba(110,191,117,.35); background: rgba(255,255,255,.9); }
.drop-icon{ width:40px; height:40px; flex: 0 0 40px; }
.dz-text{ display:flex; flex-direction:column; gap:4px; }
.dz-title{ font-weight:700; font-size: 15px; }
.dz-sub{ font-size:12px; color: color-mix(in oklab, var(--muted) 75%, white); }input[type="file"]{
position:absolute; inset:0; opacity:0; cursor:pointer; width:100%; height:100%;
}
.preview{
display:grid; grid-template-columns: 1fr; gap:10px; align-items:start;
}
.preview img{
width:100%; height:auto; max-height: 55vh; object-fit: contain;
border-radius: 12px;
background: #f8fff2;
border:1px solid rgba(59, 120, 74, .18);
}
.controls{
display:flex; gap:10px; flex-wrap:wrap; align-items:center; justify-content:space-between;
}
.left-controls, .right-controls{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.btn{
appearance:none; border:0; border-radius: 999px;
padding: 10px 14px; font-weight: 700; letter-spacing:.2px;
background: linear-gradient(90deg, var(--accent), var(--accent-2));
color:#05370f; box-shadow: var(--shadow); cursor:pointer; transition:.15s ease; display:flex; gap:8px; align-items:center;
}
.btn:active{ transform: translateY(1px); }
.btn.secondary{ background: #ffffff; color:#2a5532; border:1px solid rgba(59,120,74,.2); }
.btn.danger{ background: #ffecec; color:#7a2222; border:1px solid rgba(181,82,82,.35); }
.format{
display:flex; gap:8px; align-items:center; background:#ffffff; border:1px solid rgba(59,120,74,.15);
padding:6px 10px; border-radius:999px; box-shadow: var(--shadow);
font-size: 13px;
}
.format label{ display:flex; gap:6px; align-items:center; padding:6px 8px; border-radius: 999px; cursor:pointer; }
.format input{ accent-color:#6ebf75; }
.info{
font-size:12px; color: color-mix(in oklab, var(--muted) 70%, white);
display:flex; gap:10px; flex-wrap:wrap; align-items:center; line-height:1.4;
}
.output card{
display:block;
}
textarea{
width:100%; min-height: 36vh; resize: vertical; padding:12px 12px; line-height:1.35; border-radius: 12px;
border:1px solid rgba(59,120,74,.2); background: #fbfff6; outline: none; box-shadow: inset 0 2px 4px rgba(0,0,0,.03);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 13px;
}
footer{ text-align:center; font-size:12px; color: color-mix(in oklab, var(--muted) 64%, white); padding: 10px 0 0; }
/* 小屏优化(手机竖屏) */
@media (max-width: 480px){
.controls{ gap:8px; }
.btn{ padding: 10px 12px; }
.format{ width:100%; justify-content:center; }
.left-controls{ width:100%; justify-content:center; }
.right-controls{ width:100%; justify-content:center; }
}
/* toast */
.toast{ position: fixed; z-index: 50; left: 50%; bottom: 24px; transform: translateX(-50%) translateY(16px);
background: #103e1b; color: #e9ffe9; padding: 10px 14px; border-radius: 999px; opacity:0; pointer-events:none;
transition: .25s ease; box-shadow: var(--shadow); font-size: 13px; }
.toast.show{ opacity:1; transform: translateX(-50%) translateY(0); }
</style>
</head>
<body>
<div class="container">
<header>
<h1>图片转化 base64 编码</h1>
<div class="subtitle">上传或拖拽图片,立即生成 Base64 / Data URL支持一键复制与清空。已针对手机竖屏优化。</div>
</header><section class="card uploader">
<div class="dropzone" id="dropZone" tabindex="0" aria-label="点击或拖拽图片到此处上传">
<svg class="drop-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 16v-8M8 8l4-4 4 4" stroke="#568f5b" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="3" y="12" width="18" height="8" rx="3" stroke="#85c076" stroke-width="1.4" />
</svg>
<div class="dz-text">
<div class="dz-title">点击选择图片或直接拖拽到这里</div>
<div class="dz-sub">支持 PNG / JPG / GIF / WebP / SVG 等常见格式</div>
</div>
<input id="fileInput" type="file" accept="image/*" />
</div>
<div class="preview" id="previewWrap" hidden>
<img id="preview" alt="图片预览" />
<div class="info" id="info"></div>
<div class="controls">
<div class="left-controls format" role="radiogroup" aria-label="输出格式">
<label><input type="radio" name="format" value="dataurl" checked> Data URL含前缀</label>
<label><input type="radio" name="format" value="base64"> 仅 Base64不含前缀</label>
</div>
<div class="right-controls">
<button class="btn" id="copyBtn" type="button" title="复制到剪贴板">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M9 9h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2Z" stroke="#234b2d" stroke-width="1.6"/><path d="M7 15H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1" stroke="#234b2d" stroke-width="1.6"/></svg>
复制
</button>
<button class="btn danger" id="clearBtn" type="button" title="清空当前内容">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 7h16M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2M6 7l1.2 12.1A2 2 0 0 0 9.2 21h5.6a2 2 0 0 0 2-1.9L18 7" stroke="#8a3737" stroke-width="1.6" stroke-linecap="round"/></svg>
清空
</button>
</div>
</div>
<div class="output card">
<textarea id="output" placeholder="这里将显示转换后的内容……" readonly></textarea>
</div>
</div>
</section>
<footer>本工具在浏览器本地完成转换,不会上传图片或保存数据。</footer>
</div> <div class="toast" id="toast" role="status" aria-live="polite"></div> <script>
const fileInput = document.getElementById('fileInput');
const dropZone = document.getElementById('dropZone');
const previewWrap = document.getElementById('previewWrap');
const previewImg = document.getElementById('preview');
const infoEl = document.getElementById('info');
const output = document.getElementById('output');
const copyBtn = document.getElementById('copyBtn');
const clearBtn = document.getElementById('clearBtn');
const formatRadios = document.querySelectorAll('input[name="format"]');
const toast = document.getElementById('toast');
let originalDataUrl = '';
function showToast(text){
toast.textContent = text;
toast.classList.add('show');
setTimeout(()=> toast.classList.remove('show'), 1800);
}
function humanSize(bytes){
if(bytes < 1024) return bytes + ' B';
const units = ['KB','MB','GB'];
let i = -1; do { bytes = bytes / 1024; i++; } while(bytes >= 1024 && i < units.length-1);
return bytes.toFixed(bytes < 10 ? 2 : 1) + ' ' + units[i];
}
function setOutputByFormat(){
if(!originalDataUrl) return;
const base64 = originalDataUrl.split(',')[1] || '';
const format = document.querySelector('input[name="format"]:checked')?.value || 'dataurl';
output.value = format === 'base64' ? base64 : originalDataUrl;
}
function updateInfo(file){
const base64Len = (originalDataUrl.split(',')[1] || '').length;
const approxBytes = Math.floor(base64Len * 3/4); // 估算
const tip = base64Len > 3_000_000 ? '(较大,复制可能稍慢)' : '';
infoEl.innerHTML = `
<span><strong>文件:</strong>${file.name}</span>
<span><strong>类型:</strong>${file.type || '未知'}</span>
<span><strong>原大小:</strong>${humanSize(file.size)}</span>
<span><strong>Base64 长度:</strong>${base64Len.toLocaleString()} 字符 ≈ ${humanSize(approxBytes)}</span>
<span>${tip}</span>
`;
}
function handleFile(file){
if(!file) return;
if(!file.type.startsWith('image/')){
showToast('请选择图片文件');
return;
}
const reader = new FileReader();
reader.onload = (e)=>{
originalDataUrl = String(e.target.result || '');
previewImg.src = originalDataUrl;
previewWrap.hidden = false;
setOutputByFormat();
updateInfo(file);
};
reader.onerror = ()=> showToast('读取文件失败');
reader.readAsDataURL(file);
}
// 文件选择
fileInput.addEventListener('change', (e)=> handleFile(e.target.files?.[0]));
// 拖拽上传
['dragenter','dragover'].forEach(ev=> dropZone.addEventListener(ev, (e)=>{ e.preventDefault(); e.dataTransfer.dropEffect='copy'; dropZone.classList.add('dragover'); }));
;['dragleave','drop'].forEach(ev=> dropZone.addEventListener(ev, (e)=>{ e.preventDefault(); dropZone.classList.remove('dragover'); }));
dropZone.addEventListener('drop', (e)=>{
const file = e.dataTransfer.files?.[0];
handleFile(file);
});
// 键盘无障碍:回车打开文件选择
dropZone.addEventListener('keydown', (e)=>{
if(e.key === 'Enter' || e.key === ' '){
e.preventDefault();
fileInput.click();
}
});
// 切换输出格式
formatRadios.forEach(r => r.addEventListener('change', setOutputByFormat));
// 复制
copyBtn.addEventListener('click', async ()=>{
if(!output.value){ showToast('没有可复制的内容'); return; }
try{
await navigator.clipboard.writeText(output.value);
showToast('已复制到剪贴板');
}catch(err){
// 兼容:选中文本让用户手动复制
output.select();
const ok = document.execCommand?.('copy');
showToast(ok ? '已复制到剪贴板' : '复制失败,请手动复制');
}
});
// 清空
clearBtn.addEventListener('click', ()=>{
originalDataUrl = '';
output.value = '';
previewImg.removeAttribute('src');
previewWrap.hidden = true;
fileInput.value = '';
showToast('已清空');
});
// 粘贴图片(可选加分功能)
window.addEventListener('paste', (e)=>{
const items = e.clipboardData?.items || [];
for(const it of items){
if(it.type.startsWith('image/')){
const file = it.getAsFile();
handleFile(file);
showToast('已从剪贴板粘贴图片');
break;
}
}
});
</script></body>
</html>