327 lines
13 KiB
HTML
327 lines
13 KiB
HTML
<!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> |