把 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 输出"这三步,剩下的都是细节。

细节才是花时间的地方。

相关推荐
竹林8181 天前
把神经网络塞进一个浏览器标签页:端侧视觉 AI 的工程真相
前端·浏览器
竹林81812 天前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
嘉琪coder18 天前
我做了一个 Chrome 扩展,把 YouTube 播放列表批量变成 AI 可读的本地 Markdown
chrome·开源·浏览器
若丶相见19 天前
Codex、Claude Code、WorkBuddy + Tabbit CLI:让 AI 操控浏览器发文章
人工智能·浏览器
围炉聊科技20 天前
Playwright Test Agents 三件套实测 ——智能体基建系列
浏览器·ai编程·测试
竹林81820 天前
现代浏览器插件早已不是小脚本:从 MV3 架构、跨进程通信到端侧 AI 的工程化实战
浏览器
贝锐24 天前
贝锐洋葱头浏览器技术解析:企业多账号管理与安全隔离实践
浏览器
律宏阔1 个月前
CloakBrowser 开发踩坑笔记
前端·浏览器
Y3815326621 个月前
body-upload-js
前端·javascript·浏览器·深拷贝·数据克隆