把 10 个小工具做成"单文件网页":不装、不联网、不上传,我踩过的 7 个坑

前一阵子想给自己的图片处理流程做点趁手的小工具。要求很土:双击就能用,不装软件,不联网,照片不上传。

做完之后是 10 个 html 文件,每个 7--12KB,加起来不到 90KB。它们不需要服务器,因为它们整个程序就是那个文件本身。

这篇不吹功能,只说技术上真正花时间的地方,以及我踩过的坑。

一、为什么坚持"单文件 + 本地处理"

在线工具的问题不是功能不够,而是你的数据要经过别人的服务器。压缩一张身份证照片、给合同加水印,这种事交给一个陌生网站,风险和收益完全不成比例。

浏览器其实自带全部能力:canvas 能重绘和编码图片,File API 能读本地文件,Blob 能直接给下载链接。这些都不需要网络。所以整件事可以在一台断网的电脑上完成。

二、坑 1:把"压缩"做成"压到指定大小",不能只调画质

最早的压缩工具只有一个质量滑杆。问题是有很多场景不是"越小越好",而是**"必须小于某个数"**:报名系统要求 100KB 以内、公众号要求 2MB 以内。

于是加了"目标大小"模式:二分搜索画质。第一版上线我就拿一张 1600×1200 的噪点图测,目标 20KB------

结果它输出 58.3 KB → 58.3 KB,什么都没做。

原因:画质降到 0.25,那张图还有 50KB 以上。单靠画质根本到不了目标,于是代码走了"压不动就保留原图"的兜底分支,用户看到的等于失败。

改成两条轴一起搜 :外层逐档缩小尺寸(1 → 0.8 → 0.65 → 0.5 → 0.4 → 0.32),内层在每档里二分画质,取"满足目标里质量最高的那个"。同一张图,同样目标 20KB,输出 19.9KB。

教训很直白:降画质和降分辨率是两个独立手段,只用一个,就一定有一类图压不动。

三、坑 2:圆角 + 阴影的绘制顺序

给商品图做圆角白底主图时,我先画了圆角路径、clip(),再给图片加 shadowBlur。

结果四个角外面渗出灰边。因为阴影是跟着图片的矩形边界绘制的,圆角裁剪只裁了图片本身,没裁阴影。

正确顺序是:先画一层"带阴影的圆角块"当影子,再在它上面画真正的图片。

js 复制代码
// ① 影子:黑色圆角矩形 + blur,不画进最终图像的整体裁剪里
ctx.save();
ctx.shadowColor = 'rgba(0,0,0,.45)';
ctx.shadowBlur = 26;
ctx.shadowOffsetY = 9;
ctx.fillStyle = '#000';
roundRect(ctx, x, y, w, h, r);
ctx.fill();
ctx.restore();

// ② 图片:同样的圆角路径,clip 后绘制
ctx.save();
roundRect(ctx, x, y, w, h, r);
ctx.clip();
ctx.drawImage(img, x, y, w, h);
ctx.restore();

验收方式我也换了:不看"看起来对不对",而是读像素------四个角取点应该是背景色(255,255,255),中心点应该等于原图颜色。这样改动前后能一眼对比。

四、坑 3:九宫格切图不能直接切

把一张图切九宫格发朋友圈,看起来是"分成 9 份",但手机拍的照片几乎都是 4:3 或 16:9,非方形直接切,每一格都是变形的。

做法是先把原图居中裁成正方形,再等分。多出来的部分会被丢掉,但九格拼回去仍然是一张正常的正方形图。

顺带做了个"切割线预览"------原图上叠一层网格,切之前就知道会切在哪。

五、坑 4:交付要加密,"base64 内嵌"不算加密

这是我自己最尴尬的一个发现。

一开始想把 4 个工具打包成"一个文件"发给别人,做法是把每个工具 base64 后塞进 iframe 的 srcdoc。它能用,看起来很优雅。

但它不是加密 。任何人打开开发者工具,一行 atob() 就把你的东西全拿走了。我自己用一行正则 + 解码就验证了这一点:atob 出来就是完整的 <!DOCTYPE html>。

改成了正经的做法:PBKDF2(20 万次)+ AES-256-GCM ,Python 侧用 cryptography 加密,浏览器侧用 WebCrypto 解密。好处是全程离线------密码在本地参与派生,密钥不会离开这台机器,也不需要任何后端。

配套我还放了两个密码:试用密码 (公开写在页面上,解锁一个小游戏)和正式密码。这样别人可以先玩上,再决定要不要。

六、坑 5:Monaco 编辑器没法用 textarea.value 填内容

这个是给在线代码平台粘贴代码时踩的。

我先找页面里的 textarea,把 value 设进去,事件也派发了。页面看起来接受了,textarea.value 读回来确实是我的内容。

但页面顶部显示"无更新",编辑器里还是原来的 hello world。

原因:Monaco 有自己的 model ,那个 textarea 只是给输入法用的隐藏输入框。更坑的是------它的 value 只有几十个字符(当前行的片段),我一度以为"内容只粘进去 60 个字符"。

正解只有一条:真实交互 。点进编辑器让它获得焦点,Ctrl+A 全选,然后真实按键输入或剪贴板粘贴。150KB 的内容用剪贴板一次性粘贴是可行的。

七、坑 6:把"页面显示了"当成"别人能用了"

我以为把文件上传到某个网盘、页面显示文件列表,就算交付完成了。

后来我用不带任何 Cookie 的请求 去抓那个下载接口,发现返回的是页面外壳;点开文件行,页面自己写着 This file could not be loaded.

"页面显示了"和"真的能用"是两件事。

现在我的验收标准固定成两条:①不带登录态 的请求能不能取回内容;②在真浏览器里真的跑一遍(图形类工具就直接读像素)。两条都过才算数。

八、坑 7:删除数据之前先确认它是不是你要删的

最后一个和技术无关。

我为了让页面"回到干净状态",在一次粘贴之后直接刷新了页面------粘进去的内容全没了,因为那个页面并没有自动保存草稿。

从那以后我的规矩是:在"有未保存内容"的页面上,刷新不是重置,是丢数据。

结果

10 个工具,总计 90KB 左右:两个小游戏(华容道、记忆翻牌),八个图片/文本工具(压缩、加水印、转格式并压到指定大小、九宫格切图、拼接、裁剪旋转、商品主图、文本工具箱)。

它们都在一个页面里,打开就能用,不需要下载:

在线试用(免费版):code.juejin.cn/pen/7690544...

如果你想自己改,思路就是把上面那些 canvas 和 File API 的组合拆开------每个工具本质上都只是"读入一个文件、在 canvas 上重画、toBlob 输出"这三步,剩下的都是细节。

细节才是花时间的地方。

相关推荐
SoulRoar.18 小时前
Armbian 上为虚拟桌面安装 Chromium 浏览器
浏览器·armbian·虚拟显示器
Summer-Bright1 天前
浏览器成了AI应用的新入口:智能体接管标签页之后,三本账谁买单
人工智能·chatgpt·agent·浏览器·智能体
ShineWinsu3 天前
对于CSS:弹性布局(Flexbox)的解析
前端·css·面试·浏览器·web·网页·弹性布局
Amos_Web4 天前
我做了一个 Chrome 插件,专治“这篇文章我明明看过,但重点去哪了?”
前端·浏览器
Ikalus19885 天前
我给自己写的工具,连着骗了我七次
chrome·浏览器
ShineWinsu7 天前
对于HTML:HTML常见标签的解析—上
前端·面试·html·浏览器·标签·图片·超链接
Frag0ut14 天前
Chrome 浏览器安全指南:常见风险、隐私设置与防护建议
chrome·浏览器·隐私保护·安全防护·账号安全·安全设置·扩展权限
Frag0ut15 天前
Chrome四大版本获取及共存指南:Stable/Beta/Dev/Canary
前端·chrome·浏览器·dev·beta·canary·共存版
Frag0ut16 天前
新兴浏览器内核能和 Chrome 有一战之力吗?Servo、Ladybird、WebKit 与 Gecko 的技术格局分析
chrome·浏览器·webkit·blink·gecko·servo·ladybird
Helix25016 天前
Chrome 快捷键效率提升:10 个常用快捷键,把浏览速度提升一个量级
chrome·google·浏览器·使用技巧·效率工具·快捷键·爆红教程