插画:打开的工具箱里弹出计算器、尺子、时钟、文档与代码符号,右上角是离线 WiFi 图标
前端离线可用单文件开源项目MiniTools

十一个工具,零网络请求:MiniTools 的离线思路

MiniTools 现在有 11 个工具。图片裁切、批处理、加水印、打码、拼图、抠图,JSON 格式化、流程图、HTML 转 PDF、表格合并、条码生成。

它最特别的地方不是功能,是一条硬规矩:打开到导出,一次网络请求都不发。

在线版 kingsir.work/MiniTools/ 也是这样。断网、飞行模式、公司内网被墙、CDN 被抽风 —— 它都照跑不误。

这篇讲这个决定是怎么来的,以及为什么它其实挺贵的。

起因:一次很小的尴尬

我原本只是想写个图片拼接的工具自己用。写完顺手放网上,分享给同事。

第二天有人跟我说:打不开了。

排查下来是 CDN 的问题。问题不大,但它让我意识到一件事 —— 一个”处理本地文件”的工具,跑起来却依赖一个别人家的服务器,这个设计本身就是拧的。

我用它处理的是我自己的图片、我的表格。这些数据从头到尾都在我机器上。结果打开页面这一步,反倒要经过别人的服务器才能开始。

这说不通。所以我决定不这么玩了。

做法:把依赖搬进自己家里

MiniTools 里用到的第三方库有 Mermaid、SheetJS、JSZip、heic2any、html2pdf、JsBarcode、Vue 3。

正常做法是走 CDN,一行 <script src="..."> 完事。改成全部本地化,就是把每个库的文件塞进对应工具的 vendor/ 目录,然后引本地路径。

改完的代价是这样的:

工具 本地依赖 体积
流程图编辑器 Mermaid 约 3.2 MB
图片批处理 heic2any + JSZip 约 1.4 MB
表格合并 SheetJS + Vue 3 约 1.0 MB
HTML 转 PDF html2pdf.bundle 约 885 KB
条码生成器 JsBarcode + JSZip 约 155 KB
图片裁切、图片加水印 JSZip 各约 95 KB
抠图、打码、拼图、JSON 格式化 无 0

流程图编辑器最惨,为了一个 Mermaid 背上 3.2 MB。移动网络下打开大概要一秒多的等待。

有人问过:值吗?加载慢换取断网可用,到底哪个更重要?

我的判断是要看工具的性质。图片拼接这种事,我随时随地做,地铁里、飞机上、山里,都可能。慢一秒我能忍,打不开我忍不了。

而且这个”慢”只发生在第一次。之后浏览器缓存就吃上了。

一个自己踩的坑:不是所有工具都需要 JSZip

一开始我图省事,十一个工具里凡是涉及多文件导出的,全部塞上 JSZip。

后来发现不对。

JSZip 是干什么的?把多个文件打包成 ZIP。那如果一个工具只导出一个文件呢?比如图片裁切,勾了三张图,导出三个独立 PNG —— 那就是三个下载,根本不需要”打包”这个动作。

于是我调整了策略:只有真正需要”打成一个文件”才引 JSZip,其余走原生。

原生打包长什么样:

// 没有 JSZip 的地方,直接用平台 API
function downloadOne(blob, filename) {
  const a = document.createElement("a");
  a.href = URL.createObjectURL(blob);
  a.download = filename;
  a.click();
  URL.revokeObjectURL(a.href);   // 记得回收,不然后面会卡
}

需要打包多个的时候,浏览器有原生接口:

// 批量导出:一次触发多个下载
urls.forEach((u, i) => setTimeout(() => triggerDownload(u, names[i]), i * 120));

那个 i * 120 是有讲究的 —— 浏览器会拦截”连续多次自动下载”,间隔开就能逐个触发。不加这个延迟,用户点了导出只下来一个文件,然后以为坏了。

抠图、打码、拼图、JSON 格式化这四个现在依赖是 0,加载打开即用。

数据不出浏览器,是顺便获得的

做”零网络请求”的时候,我没想到会附带一个副产品:数据天然不可能泄露。

因为没有任何上传接口,图片、表格、JSON 全都在页面的内存里处理完就走。你传给它的东西,物理上就没有离开过你的电脑。

这年头说”数据本地处理”的话挺多的,验证成本却很高 —— 你得读代码,或者抓包。

而”整个页面不发出一个请求”这件事,是可以当场验证的:打开开发者工具的网络面板,一路操作到底,面板干干净净。一句话说清,不用解释。

界面:跟所有工具保持一致

技术上有意思的部分到此为止,说点务实的。

十一个工具如果各有各的按钮风格、深浅色方案、语言切换逻辑,那就不是一个”工具集”,是十一个割裂的小网页。用的人得重新适应十一次。

所以统一了三件事:

深浅双主题。 默认跟系统,右上角能切,选择记在 localStorage。导航页和十一个工具页行为完全一致 —— 这点很重要,要是首页能切、内页不能,那就裂开了。

中英双语。 每个页面右上角 中 / EN,选择记在 lang,默认跟随浏览器语言。十二个页面共用同一套对照表。

视觉语言。 对齐近黑前景 #171717、纯白底、细灰边框、黑底胶囊主按钮。用 GitHub / Vercel 那套审美,因为它已经被验证过足够耐看,不用我再调。

配色上反馈有统一规则:成功和信息用前景反色(黑底白字),只有错误用红色。这样页面里出现红色的地方,一定是出事了,不用自己去找。

顺带说一句抠图

图片抠图那 个工具我原本以为会是纯靠库。看了源码才发现是自己写的算法,核心就三行:

band = clamp(round(min(W, H) * 0.04), 2, 128);   // 只采样四条边的窄带
d    = sqrt(dr² + dg² + db²) / √3;                // 归一化到 0-255 的颜色距离
d <= t        → alpha = 0                          // 认定为背景
d >= t + soft → alpha = 255                        // 认定为前景
其余          → alpha = 255 * (d - t) / soft       // 中间地带羽化过渡

逻辑是:抠图的本质是”判断哪些像素是背景”,而背景色几乎总是出现在画面四边。所以只采样边缘一圈窄带(宽高的 4%,2~128px 之间),做直方图取频次最高的几个颜色当候选背景。

这里有个细节我挺欣赏的:勾选”保留主体内部同色区”时,它会从四边做一次泛洪填充,只有跟边缘连通的才算背景。所以白衬衫上的白、白背景里的白,能分开。

手动擦除和恢复记在独立的 delta 层里,不跟计算结果混在一起。所以你调完容差重新算,之前手工擦的地方还在。

这种”算法简单但把边界情况想清楚”的感觉,是我觉得 AI 写代码最出彩的时候 —— 不是难,是细。

最后

MIT 许可,随意改。仓库在 isnotry/MiniTools,每个工具目录下都有一份 README 说明功能与依赖。

如果你发现哪个工具缺了什么功能,或者觉得某个交互别扭,直接提 issue 或者改了推上来都行。