当前位置: 首页 > news >正文

实用 html 小工具

图片加边框

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>批量图片加灰色渐变相框</title><style>:root{--bg:#0f1115;--panel:#151821;--muted:#8087a2;--accent:#4f8cff;--text:#e8ecf1;--card:#0d0f14;--border:#242837;}*{box-sizing:border-box}html,body{height:100%}body{margin:0;background:linear-gradient(180deg,#0c0f14 0%,#0b0d12 100%);color:var(--text);font:15px/1.4 system-ui,Segoe UI,Roboto,Helvetica,Arial,"Apple Color Emoji","Segoe UI Emoji"}header{position:sticky;top:0;z-index:5;background:rgba(13,15,20,.7);backdrop-filter:saturate(140%) blur(8px);border-bottom:1px solid var(--border)}.wrap{max-width:1100px;margin:0 auto;padding:18px}h1{font-size:20px;margin:0}.controls{display:grid;grid-template-columns:repeat(12,1fr);gap:12px;margin-top:12px}.card{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:14px}.ctrl{display:flex;flex-direction:column;gap:8px}.ctrl label{font-size:12px;color:var(--muted)}input[type="number"],select,input[type="color"],button{width:100%;padding:10px;border-radius:10px;border:1px solid var(--border);background:var(--card);color:var(--text)}button{cursor:pointer;border:1px solid #2c3347}button.primary{background:linear-gradient(180deg,#3a79ff,#2f67da);border:none}button.ghost{background:transparent;border:1px dashed #2c3347}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px;margin:18px 0 80px}.item{background:var(--panel);border:1px solid var(--border);border-radius:14px;overflow:hidden}.thumb{display:flex;align-items:center;justify-content:center;background:#0b0d12}.thumb canvas{max-width:100%;height:auto;display:block}.meta{padding:10px;display:flex;gap:8px}.meta button{flex:1}.drop{display:flex;align-items:center;justify-content:center;border:2px dashed #39508a;border-radius:14px;padding:28px;color:#aab2cc;background:#0c1220}.drop.drag{border-color:#6aa2ff;background:#0e1730}footer{position:fixed;left:0;right:0;bottom:0;background:rgba(13,15,20,.85);backdrop-filter:blur(8px);border-top:1px solid var(--border)}.bar{display:flex;gap:10px;align-items:center;justify-content:space-between}.left, .right{display:flex;gap:10px;align-items:center}.hint{font-size:12px;color:#93a0c3}.hidden{display:none !important}</style>
</head>
<body><header><div class="wrap"><h1>批量图片加灰色渐变相框</h1><div class="controls"><div class="card ctrl" style="grid-column:span 5"><label>选择图片(可多选)</label><div class="drop" id="drop">将图片拖拽到此处,或<label style="margin-left:8px"><input id="file" type="file" accept="image/*" multiple class="hidden"> <button class="ghost" id="pickBtn" type="button">浏览文件</button></label></div><span class="hint">支持 JPG、PNG、WebP、BMP、GIF(取首帧)</span></div><div class="card ctrl" style="grid-column:span 7"><label>相框样式</label><div style="display:grid;grid-template-columns:repeat(6,1fr);gap:10px"><div class="ctrl"><label>相框宽度(px)</label><input id="frameWidth" type="number" min="1" max="300" value="28"></div><div class="ctrl"><label>圆角半径(px)</label><input id="radius" type="number" min="0" max="200" value="14"></div><div class="ctrl"><label>渐变类型</label><select id="gradType"><option value="radial">径向渐变</option><option value="linear">线性渐变</option></select></div><div class="ctrl"><label>外侧颜色</label><input id="outerColor" type="color" value="#3a3a3a"/></div><div class="ctrl"><label>内侧颜色</label><input id="innerColor" type="color" value="#bdbdbd"/></div><div class="ctrl"><label>阴影强度</label><select id="shadowLevel"><option value="0"></option><option value="1" selected></option><option value="2"></option><option value="3"></option></select></div></div><div style="margin-top:10px;display:flex;gap:10px"><button class="primary" id="applyAll" type="button">应用相框</button><button id="clearAll" type="button">清空列表</button></div></div></div></div></header><main class="wrap"><div id="list" class="grid"></div></main><footer><div class="wrap bar"><div class="left"><button id="downloadAll" class="primary" type="button">全部打包下载</button><span class="hint" id="countHint">尚未添加图片</span></div><div class="right"><span class="hint">相框建议:外深内浅灰,保持 CS——哦不,是保持整体风格统一 🙂</span></div></div></footer><!-- 可选:打包下载依赖(在线) --><script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script><script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script><script>const el = {file: document.getElementById('file'),pickBtn: document.getElementById('pickBtn'),drop: document.getElementById('drop'),list: document.getElementById('list'),applyAll: document.getElementById('applyAll'),clearAll: document.getElementById('clearAll'),downloadAll: document.getElementById('downloadAll'),countHint: document.getElementById('countHint'),frameWidth: document.getElementById('frameWidth'),radius: document.getElementById('radius'),gradType: document.getElementById('gradType'),outerColor: document.getElementById('outerColor'),innerColor: document.getElementById('innerColor'),shadowLevel: document.getElementById('shadowLevel'),};const items = []; // { file, name, img, canvas }// ---------- UI helpers ----------function updateCount(){el.countHint.textContent = items.length ? `共 ${items.length} 张图片` : '尚未添加图片';}function addFiles(files){const arr = Array.from(files || []).filter(f => /^image\//.test(f.type));if(!arr.length) return;arr.forEach(file => addItem(file));updateCount();}function addItem(file){const url = URL.createObjectURL(file);const img = new Image();img.crossOrigin = 'anonymous';img.onload = () => {renderItem({file, name:file.name.replace(/\.(\w+)$/, ''), img});URL.revokeObjectURL(url);};img.src = url;}function renderItem(obj){items.push(obj);const wrap = document.createElement('div');wrap.className = 'item';wrap.innerHTML = `<div class="thumb"><canvas></canvas></div><div class="meta"><button class="ghost one">下载</button><button class="ghost rerender">重绘</button><span class="hint" style="margin-left:auto">${escapeHtml(obj.name)}</span></div>`;obj.canvas = wrap.querySelector('canvas');el.list.prepend(wrap);drawWithFrame(obj); // 初次渲染wrap.querySelector('.one').addEventListener('click', async()=>{const blob = await canvasToBlob(obj.canvas);saveAs(blob, `${obj.name}_framed.png`);});wrap.querySelector('.rerender').addEventListener('click',()=> drawWithFrame(obj));}// ---------- Core drawing ----------function drawWithFrame(obj){const fw = clamp(parseInt(el.frameWidth.value||0,10), 0, 300);const r  = clamp(parseInt(el.radius.value||0,10), 0, 400);const type = el.gradType.value;const outer = el.outerColor.value;const inner = el.innerColor.value;const shadow = parseInt(el.shadowLevel.value,10);const img = obj.img;const w = img.naturalWidth + fw*2;const h = img.naturalHeight + fw*2;const c = obj.canvas;c.width = w; c.height = h;const ctx = c.getContext('2d');ctx.clearRect(0,0,w,h);// 背景 + 渐变相框(外深内浅)const grad = (type === 'radial')? radialGrad(ctx, w, h, fw, outer, inner): linearGrad(ctx, w, h, fw, outer, inner);// 画圆角外框roundRectPath(ctx, 0.5, 0.5, w-1, h-1, r);ctx.fillStyle = grad;ctx.fill();// 可选阴影(内缘轻微暗角)if(shadow>0){const alpha = [0, 0.10, 0.17, 0.24][shadow];const g2 = ctx.createRadialGradient(w/2,h/2,Math.max(w,h)/3, w/2,h/2, Math.max(w,h)/1.2);g2.addColorStop(0, `rgba(0,0,0,0)`);g2.addColorStop(1, `rgba(0,0,0,${alpha})`);roundRectPath(ctx, 0.5, 0.5, w-1, h-1, r);ctx.fillStyle = g2;ctx.fill();}// 镂空内窗ctx.save();ctx.globalCompositeOperation = 'destination-out';roundRectPath(ctx, fw + 0.5, fw + 0.5, img.naturalWidth -1, img.naturalHeight -1, Math.max(0, r - Math.min(r, fw)));ctx.fill();ctx.restore();// 绘制图片(裁切到内窗)ctx.save();roundRectPath(ctx, fw, fw, img.naturalWidth, img.naturalHeight, Math.max(0, r - Math.min(r, fw)));ctx.clip();ctx.drawImage(img, fw, fw);ctx.restore();}function roundRectPath(ctx, x,y,w,h,r){const rr = Math.min(r, w/2, h/2);ctx.beginPath();ctx.moveTo(x+rr, y);ctx.arcTo(x+w, y, x+w, y+h, rr);ctx.arcTo(x+w, y+h, x, y+h, rr);ctx.arcTo(x, y+h, x, y, rr);ctx.arcTo(x, y, x+w, y, rr);ctx.closePath();}function radialGrad(ctx, w,h, fw, outer, inner){const g = ctx.createRadialGradient(w/2,h/2, Math.max(8, Math.min(w,h)/8), w/2,h/2, Math.max(w,h)/2);g.addColorStop(0, inner);g.addColorStop(1, outer);return g;}function linearGrad(ctx, w,h, fw, outer, inner){const g = ctx.createLinearGradient(0,0,w,h);g.addColorStop(0, outer);g.addColorStop(0.5, inner);g.addColorStop(1, outer);return g;}function clamp(n,min,max){return Math.max(min, Math.min(max,n))}function escapeHtml(s){return s.replace(/[&<>"']/g, m=>({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[m]))}function canvasToBlob(canvas){return new Promise(res=>canvas.toBlob(b=>res(b),'image/png'))}// ---------- Events ----------el.pickBtn.addEventListener('click',()=> el.file.click());el.file.addEventListener('change', e=> addFiles(e.target.files));;['dragenter','dragover'].forEach(t=> el.drop.addEventListener(t, e=>{e.preventDefault(); e.dataTransfer.dropEffect='copy'; el.drop.classList.add('drag')}));;['dragleave','drop'].forEach(t=> el.drop.addEventListener(t, e=>{e.preventDefault(); el.drop.classList.remove('drag')}));el.drop.addEventListener('drop', e=> addFiles(e.dataTransfer.files));el.applyAll.addEventListener('click', ()=> items.forEach(drawWithFrame));el.clearAll.addEventListener('click', ()=>{ items.length=0; el.list.innerHTML=''; updateCount(); });el.downloadAll.addEventListener('click', async()=>{if(!items.length) return;if(!(window.JSZip && window.saveAs)){ alert('缺少打包依赖,已自动改为逐张下载。'); for(const it of items){ const b=await canvasToBlob(it.canvas); saveAs(b, `${it.name}_framed.png`);} return; }const zip = new JSZip();const folder = zip.folder('framed');for(const it of items){await new Promise(r => setTimeout(r,0));const blob = await canvasToBlob(it.canvas);folder.file(`${it.name}_framed.png`, blob);}const content = await zip.generateAsync({type:'blob'});saveAs(content, `framed_${new Date().toISOString().slice(0,10)}.zip`);});</script>
</body>
</html>

在这里插入图片描述


文章转载自:

http://AaXfUN8b.rkmsm.cn
http://dLyXsKyL.rkmsm.cn
http://5NcHCkRD.rkmsm.cn
http://ZakIar2z.rkmsm.cn
http://AKbCG8u3.rkmsm.cn
http://XVSOtVLN.rkmsm.cn
http://ElhUTa2b.rkmsm.cn
http://e1Hi6hmp.rkmsm.cn
http://lXxksWn6.rkmsm.cn
http://9IV4qkag.rkmsm.cn
http://kHDjODoH.rkmsm.cn
http://7XeUhMaB.rkmsm.cn
http://3xa8drG5.rkmsm.cn
http://T4HW4nNY.rkmsm.cn
http://7AMLKj8Q.rkmsm.cn
http://sNgGa0KP.rkmsm.cn
http://HBifJWMu.rkmsm.cn
http://gAPORAOG.rkmsm.cn
http://EJMdV9cV.rkmsm.cn
http://Jd3E3PZG.rkmsm.cn
http://q7QnrZch.rkmsm.cn
http://e68yoHPz.rkmsm.cn
http://Uw2yhz7u.rkmsm.cn
http://HosyGntj.rkmsm.cn
http://TQKDwgdp.rkmsm.cn
http://yiuphv0r.rkmsm.cn
http://JOpwtNVk.rkmsm.cn
http://pCwJyhti.rkmsm.cn
http://LUIVkAGl.rkmsm.cn
http://Uoa4j2nl.rkmsm.cn
http://www.dtcms.com/a/375948.html

相关文章:

  • C#(链表创建与原地反转)
  • 光伏MPPT——拓扑结构及发波方式
  • Flink通讯超时问题深度解析:Akka AskTimeoutException解决方案
  • 美团核销接口助力第三方供应商拓展市场份额的策略
  • 基于dijkstra算法的WSN网络MAC协议matlab仿真,分析网络延迟与网络开销
  • 《Linux运维工程师基础技能测试简答题》
  • CPUID
  • aiagent知识点
  • DPO原理 | 公式推导
  • 代码随想录算法训练营第三十九天|62.不同路径 63.不同路径ll
  • Redis(主从复制)
  • 嵌入式 - ARM3
  • 【QT随笔】结合应用案例一文完美概括QT中的队列(Queue)
  • lesson57:CSS媒体查询完全指南:从基础语法到移动端响应式设计最佳实践
  • 定制 ResourceBundle 的实现与 DuiLib 思想在 Chromium 架构下的应用解析
  • 常用排序算法核心知识点梳理
  • Dubbo3序列化安全机制导致的一次生产故障
  • 《2025年AI产业发展十大趋势报告》四十七
  • 传统项目管理中如何控制进度
  • C 语言第一课:hello word c
  • Cartographer 位姿推测器pose_extrapolator
  • Matlab机器人工具箱使用5 轨迹规划
  • 【git】Git 大文件推送失败问题及解决方案
  • ctfshow-web入门-php特性(二)
  • CSP认证练习题目推荐 (1)
  • MySQL 命令
  • MyBatis操作数据库——进阶
  • huggingFace学习之编码工具
  • 人工智能期末复习(部分)
  • 【Pytorch】2025 Pytorch基础入门教程(完整详细版)