不靠框架、不联网:用 60 行代码写一个离线密码生成器(单文件 Web 工具实战)

📅 2026/7/26 4:43:15
不靠框架、不联网:用 60 行代码写一个离线密码生成器(单文件 Web 工具实战)
一、先说痛点想临时生成个密码你大概会搜一个「在线密码生成器」把需求交给它。但想一想你刚生成的口令明文是不是已经落在了对方的服务器日志里更别提那些页面先弹三个广告再偷偷引一段第三方脚本最后还顺手埋个埋点。工具越来越多但它们离你的机器、离你的数据越来越远。能不能反过来——自己写一个数据不出本机断网也能跑这一篇就带你从零做一个一个离线密码 / 口令生成器支持长度、字符规则、强度提示纯前端、零依赖最后整个东西就是一个.html文件双击即用。二、目标一个能塞进 U 盘的工具我们要交付的东西很具体选长度6–40 位、选字符集大写 / 小写 / 数字 / 符号点一下生成实时显示「熵强度」一键复制并记住你上次的长度偏好不引任何 CDN、框架、字体不联网、不上传存成一个.html就能发给同事。读完你手里就有一个能用的工具而它的全部代码大约 60 行。三、单文件工具长什么样「单文件」不是噱头而是把三件事装进一个文件结构HTML 样式CSS 逻辑JS。最小骨架长这样!doctype html html langzh-CN head meta charsetutf-8 style /* 样式写这里不引外部 css */ /style /head body !-- 结构写这里 -- script /* 逻辑写这里不引外部 js */ /script /body /html三个要点① 用系统字体栈system-ui, Microsoft YaHei, sans-serif不下载字体② 所有计算在浏览器本地完成没有fetch到任何后端③ 状态用localStorage留在本地。这就是「单文件 · 零依赖 · 本地优先」三条铁律的来历。四、实战60 行写出密码生成器下面给出完整可复制的单文件。把它整段保存成password.html双击就能用。!doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title离线密码生成器/title style :root { --bg:#0A0A0B; --card:#141417; --line:rgba(255,255,255,.08); --accent:#5E6AD2; --muted:#A1A1AA; } * { box-sizing:border-box; } body { margin:0; font:15px/1.6 system-ui,-apple-system,Segoe UI,Roboto,Microsoft YaHei,sans-serif; background:var(--bg); color:#fff; display:flex; justify-content:center; padding:32px 16px; } .app { width:100%; max-width:420px; background:var(--card); border:1px solid var(--line); border-radius:14px; padding:20px; } h1 { font-size:18px; margin:0 0 16px; } .out { font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:16px; word-break:break-all; background:#0A0A0B; border:1px solid var(--line); border-radius:8px; padding:12px; min-height:48px; } button { background:var(--accent); color:#fff; border:0; border-radius:8px; padding:10px 14px; cursor:pointer; font-size:14px; } button.ghost { background:transparent; border:1px solid var(--line); color:var(--muted); } input[typerange]{ flex:1; } label.chk { display:flex; align-items:center; gap:6px; font-size:14px; color:var(--muted); } .meta { display:flex; justify-content:space-between; color:var(--muted); font-size:13px; margin-top:6px; } .bar { height:6px; border-radius:3px; background:#222; overflow:hidden; margin-top:8px; } .bar i { display:block; height:100%; width:0; background:var(--accent); transition:width .2s; } /style /head body div classapp h1 离线密码生成器/h1 div classout idout aria-livepolite点击生成/div div classbari idbar/i/div div classmetaspan idbits强度—/spanspan idlenLabel16 位/span/div div classrow styledisplay:flex;align-items:center;gap:10px;margin:10px 0 span长度/span input typerange idlen min6 max40 value16 /div div classrow styledisplay:flex;flex-wrap:wrap;gap:10px;margin:10px 0 label classchkinput typecheckbox idup checked 大写/label label classchkinput typecheckbox idlo checked 小写/label label classchkinput typecheckbox iddg checked 数字/label label classchkinput typecheckbox idsy checked 符号/label /div div classrow styledisplay:flex;gap:10px;margin:10px 0 button idgen生成/button button idcopy classghost复制/button /div /div script const $ id document.getElementById(id); const SETS { up: ABCDEFGHJKLMNPQRSTUVWXYZ, lo: abcdefghijkmnopqrstuvwxyz, dg: 23456789, sy: !#$%^()-_[]{} }; // 用密码学安全随机数而不是 Math.random() function randInt(n){ const a new Uint32Array(1); crypto.getRandomValues(a); return a[0] % n; } function gen(){ const len $(len).value; let pool ; for (const k in SETS) if ($(k).checked) pool SETS[k]; if (!pool) { $(out).textContent 至少选一种字符; return; } let s ; for (let i0;ilen;i) s pool[randInt(pool.length)]; $(out).textContent s; const bits Math.round(len * Math.log2(pool.length)); // 熵估算 $(bits).textContent 强度 bits bits; $(bar).style.width Math.min(100, bits/64100) %; try { localStorage.setItem(pw.len, len); } catch(e){} } // 复制优先用剪贴板 APIfile:// 下回退到 execCommand function copy(){ const t $(out).textContent; if (navigator.clipboard window.isSecureContext) navigator.clipboard.writeText(t); else { const tadocument.createElement(textarea); ta.valuet; document.body.appendChild(ta); ta.select(); try{document.execCommand(copy);}catch(e){} document.body.removeChild(ta); } } $(len).oninput () $(lenLabel).textContent $(len).value 位; $(gen).onclick gen; $(copy).onclick copy; const saved localStorage.getItem(pw.len); if (saved) $(len).value saved; gen(); /script /body /html这段代码里有三个值得记住的点1随机性要用crypto.getRandomValues别用Math.random()。后者只是伪随机、可被预测拿来做口令等于没加密。crypto走的是浏览器的密码学安全随机源离线也能用。2强度按「熵」估算而不是按「看起来乱不乱」。一行公式bits 长度 × log2(字符池大小)。16 位、含四类字符大约 90 bits远超够用线64 bits 我设成进度条满格。这比「感觉挺复杂」科学得多。3偏好用localStorage留本地。你下次打开还是上次的长度——但它只存在你这台机器上不会回传任何服务器。五、让它真正「工具化」离线优先三铁律把上面的思路抽出来就是做单文件工具的三条纪律单文件整个工具一个.html可下载、可邮件、可丢 U 盘零依赖不引任何外部 CDN / 框架 / 字体断网照跑本地优先数据不出本机没有后端、没有埋点、没有账号。再加两个小习惯会更「专业」用prefers-reduced-motion尊重系统的减弱动效设置把视觉统一成一套克制的中性暗色比如上面用的#0A0A0B画布 #5E6AD2靛蓝强调色。六、收尾到这里你已经从零写出一个能用的单文件工具了——它不依赖任何库断网可用口令明文只活在你自己的浏览器里。如果你不想自己写、想直接白嫖nano-tools 矩阵里这个密码生成器的完整版叫PassForge连同另外 42 款同类单文件工具白板、JSON、哈希、正则、色彩、无障碍……都开源在 GitHub挑一个双击html就能用。留个思考题如果把这个骨架换成「UUID 生成器」「Base64 编解码」或者「JSON 格式化」改动其实不过十几行——你最想把它改造成什么