从需求到落地,聊聊如何用一个 HTML 文件做出支持 A4 标准出图、10 种形状、自动排版的警示牌工具。

一、为什么做这个
日常办公里经常需要打印各种警示牌------"请勿乱动""小心地滑""禁止吸烟"......每次都要打开设计软件、调尺寸、找图标、排版,做一张简单的牌子要花十几分钟。网上的在线工具要么有水印、要么要登录、要么导出的尺寸不够打印。
于是干脆自己写一个:纯本地、单文件、双击即用,打开浏览器就能做出 A4 大小、300DPI 的警示牌图片,直接打印。
10 种形状 · 10 套配色 · 20 个常用模板 · 60+ 表情图标 · A4/A3/A5 标准尺寸 · 一键 PNG / JPG
二、能做什么
| 功能 | 说明 |
|---|---|
| 🎨 可视化编辑 | 左侧控制面板,右侧实时预览。改文字、换形状、调颜色,所见即所得。 |
| 📐 标准尺寸出图 | A4 / A3 / A5 / 名片 / 自定义,按 DPI 换算像素,A4@300DPI = 2480×3508px,直接打印清晰。 |
| 🔷 10 种形状 | 矩形、圆角、圆形、椭圆、三角、六边形、菱形、盾牌、星形、拱形,全部矢量绘制。 |
| 🚫 国标图标 | 禁止圈、警告三角、指令圆、安全圆四种外框 + 表情,组合出标准禁烟/触电等标识。 |


三、技术实现核心
3.1 单文件架构
整个工具就是一个 .html 文件:HTML 结构 + CSS 样式 + 原生 JS,没有任何外部依赖。不加载 CDN、不打包、不构建,双击就能跑。这对一个"随时打开就能用"的小工具来说体验最好。
less
const state = {
text:'请勿乱动', sub:'谢谢配合', icon:'✋', iconStyle:'prohibited',
theme:'yellow-black', shape:'rounded', sizeKey:'a4', orient:'portrait',
dpi:300, insetPct:0, iconPct:26, bwPct:12, borderOn:true, bold:true, crop:false,
bg:'#FFD400', fg:'#141414', bd:'#141414', font:'yahei'
};
所有配置收拢在一个 state 对象里,修改任意字段后调用 scheduleRender() 重绘预览。导出时用同样的 drawSign() 函数画到一张高分辨率 canvas 上------预览和导出共用同一套渲染逻辑,保证所见即所得。
3.2 参数化布局
这是整个项目最关键的设计。所有尺寸都按画布短边 m = Math.min(W,H) 的比例计算:
javascript
function makePath(shape, W, H){
const m = Math.min(W,H);
switch(shape){
case 'circle': p.arc(cx, cy, m/2*0.98, 0, TAU); break;
case 'triangle': roundPoly(p, [[cx,H*0.015],[W*0.975,H*0.965],...], m*0.045); break;
case 'star': {
const R = m/2*0.97, rIn = R*0.382; // 标准五角星内外半径比
...
}
}
}
这样无论预览是 400px 还是导出是 2480px,渲染结果完全一致。星形只取短边做半径,避免在 A4 竖版上被纵向拉长变形------这是早期版本踩过的坑。
💡 小提示: 正五角星的内外半径比是
0.382(黄金比例的倒数减一),用这个比值画出来才是"标准"的五角星。
3.3 文字自适应排版
警示牌文字不能被形状边缘裁掉,也不能溢出。核心思路:先算出形状内最大内接矩形,再在矩形里自动换行 + 缩字号。
实现用了一个小技巧------二分法搜索 + 网格采样:
ini
function rectInside(path, x, y, w, h){
const N = 6; // 6×6 = 49 个采样点
for(let i=0;i<=N;i++) for(let j=0;j<=N;j++)
if(!probe.isPointInPath(path, x+w*i/N, y+h*j/N)) return false;
return true;
}
// 二分搜索最大缩放比例 s,使候选矩形完全落在形状内
let lo=0.06, hi=1;
for(let i=0;i<22;i++){
const mid=(lo+hi)/2;
if(rectInside(path, cx-cw*mid/2, cy-ch*mid/2, cw*mid, ch*mid)) lo=mid; else hi=mid;
}
采样点从早期的 3×3 加到 6×6(49 点),因为星形这种凹多边形,3×3 会漏掉凹边导致文字被裁。二分 22 次后精度足够,肉眼无感知。
3.4 图标系统
图标 = 表情 + 外框。外框用矢量画四种国标样式:
| 外框样式 | 绘制方式 | 适用场景 |
|---|---|---|
| 禁止圈 | 红色圆圈 + 45° 斜杠 | 禁止吸烟、禁止入内 |
| 警告三角 | 黄色填充三角形 + 黑边 | 当心触电、小心地滑 |
| 指令圆 | 蓝色实心圆 | 保持安静、请随手关门 |
| 安全圆 | 绿色实心圆 | 安全出口、急救站 |
选「禁止圈」+ 🚬,就是标准禁烟标识。表情用 Segoe UI Emoji 字体直接 fillText 画到 canvas 上,无需图片资源。
四、三个有意思的优化
4.1 导出裁掉形状外空白
A4 竖版上画一个圆形,上下会留大片空白。导出时如果想贴边用,就需要裁掉。做法是把形状画到一张低分辨率蒙版上,反查实际占用的外框:
javascript
function cropRect(shape, W, H){
// 缩放绘制到 640px 蒙版,扫描非透明像素求 min/max
const N=640, k=N/Math.max(W,H);
// ... 扫描 alpha 通道 ...
// 加上半描边补偿,避免裁掉边框
const pad = lineWidth/2 + 1.5/k;
return {x, y, w, h};
}
蒙版只有 640px,扫描极快;描边有一半在路径外侧,所以裁切框要外扩半个线宽,否则边框会被切掉。
4.2 一键复制到剪贴板
比下载更顺手的是直接复制。用 ClipboardItem 传 Promise 值,保住用户手势:
ini
const item = new ClipboardItem({
'image/png': new Promise(res => canvas.toBlob(res, 'image/png'))
});
navigator.clipboard.write([item]);
⚠️ 注意: Clipboard API 要求页面处于聚焦状态,非 https 环境下部分浏览器会拒绝。所以我保留了下载按钮作为兜底。
4.3 设置自动记忆
用户调好的文字、形状、颜色,刷新后不丢。用 localStorage 持久化 + 字段校验,防止脏数据搞崩页面:
ini
state.bwPct = Math.min(100, Math.max(0, +s.bwPct || 12));
if(SHAPE_IDS.indexOf(s.shape) >= 0) state.shape = s.shape;
if(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(s.bg)) state.bg = s.bg;
五、踩坑笔记
坑 1:边框粗细 10× 错配
滑块标签显示 0.8%,实际画的是 8%------A4 上约 16.8mm 巨粗边框,圆形直接溢出画布。根因是数值语义不统一 :绘制用 bwPct/100,标签却按 bwPct/100 显示成了 0.8%。统一改为千分比(/1000),滑块量程扩到 0--100,标签和绘制完全一致。
坑 2:五角星变形
早期把星形按 rx=W*0.485, ry=H*0.485 分别缩放,A4 竖版上被纵向拉长、横版被压扁。改回短边统一半径后,无论横竖都是正五角星。
坑 3:凹形文字被裁
3×3 采样点覆盖不了星形凹边,副提示语被截断。加到 6×6(49 点)后彻底解决。
坑 4:querySelector vs getElementById
重写文件时把 $ = document.querySelector,但调用都是 $('text')(传 id 不带 #),结果所有元素查找返回 null,整页初始化失败。改回 getElementById 就好了------重构时注意 API 语义。
六、和 TRAE 协作的过程
这个工具从头到尾是我和 AI 协作完成的。整个过程比我预想的顺畅,也有几个值得分享的点。
流程大致是这样的: 我用自然语言描述需求 → AI 出第一版代码 → 我在浏览器里试用 → 发现问题反馈给 AI → AI 定位并修复。来回几轮就成型了。
几个协作中的真实片段:
- 五角星变形:我只说了一句"五角星的形状好像有点问题",AI 就定位到是半径按宽高分别缩放导致的,改成短边统一半径后规则了。我提一句,它给方案,效率比自己查资料高很多。
- 边框粗细 10× 错配 :这个 bug 比较隐蔽------圆形在 A4 上外框溢出,我以为是裁切逻辑错了。AI 通过浏览器执行
cropRect('circle',2480,3508)拿到数值,反推发现裁切框是对的,问题出在描边粗细的标签和实际绘制差了 10 倍。这种"用数值反推根因"的排查方式,是 AI 擅长的。 - querySelector 踩坑 :我重写文件后页面全空,AI 用浏览器自动化抓控制台错误,秒定位到
$('text')返回 null,三两下就修好。 - 验证环节:AI 会自己起一个本地静态服务器,用浏览器自动化跑断言------比如"星形裁切框宽高是否在预期范围""localStorage 是否存了正确字段"。这种可复现的验证让我很放心。
整体感受:AI 不是替我写代码,而是把"想方案→写代码→跑验证→修 bug"这个循环压缩得很快。我负责提需求、做判断、试体验,它负责出实现、做验证。碰到不确定的地方,我会让它解释清楚再决定要不要改。


七、总结
这个小工具前前后后迭代了几轮,核心收获有三点:
- 参数化布局是让"小预览 = 大导出"的关键,所有尺寸按比例算,渲染逻辑只写一遍。
- 几何问题用几何思路解:内接矩形用二分+采样,裁切用蒙版反查,比硬编码坐标靠谱得多。
- 单文件零依赖对工具类产品体验极好,分享给同事直接发文件就行。
文件就是一个 HTML,双击打开就能用。想要批量出图、竖排文字、文字描边这些,后面可以继续加。
如果你也经常需要做各种标识牌,不妨自己动手写一个------比每次找模板快多了。