停止上传你的文件:我如何使用 React 与 Vite 构建了一个 100% 客户端图像转换器
Source: Dev.to
我已经厌倦了在谷歌搜索 “JPG to PNG”,结果总是跳到那些要求我把个人文件(身份证扫描件、合同等)上传到随机服务器的可疑网站,只是为了转换文件格式。
于是,我决定自己动手做一个。
目标很简单:零服务器上传。我想要一个完全在浏览器内存中运行的工具,能够即时处理文件且没有卡顿,并且在批量转换时也不会让 UI 崩溃。
The Stack
- React – UI 渲染
- Vite 6 – 快速开发服务器 & 打包工具
- Zustand – 轻量级状态管理
The Challenge: The “Serverless” Engine
为了让所有操作都在客户端完成,我利用了浏览器原生的 Canvas API,但采用了非传统的方式。整体架构像一个流水线:
- Ingestion – 与其使用旧的、慢速的
FileReader(会把图像转换成巨大的 Base64 字符串),我改用了createImageBitmap。它直接在浏览器的 GPU 内存中解码图像数据,几乎是瞬间完成。 - The “Invisible” Canvas – 我以编程方式创建一个从不加入 DOM 的
<canvas>元素。它的尺寸会匹配图像大小,将 bitmap 数据“绘制”到自身上,然后驻留在内存中。 - Extraction – 我利用 canvas 导出 Blob 为特定 MIME 类型的能力(例如
image/png或image/webp)。本质上,就是让浏览器对 canvas “拍照”,并以不同的格式输出。
Result: 一个能够在毫秒级处理 5 MB 图像的转换流水线,零网络延迟。
The Performance Trap: Infinite Loops
我选择 Zustand 进行状态管理,因为它轻量,但一开始犯了个新手错误:把主组件绑定到了整个状态对象上。
每当一张图像转换完成,状态就会更新。由于我订阅了整个对象,React 看到的是一个新的对象引用,于是重新渲染了整个列表。随着 50 张图像逐个更新状态,这导致了一连串的重新渲染,看起来像是内存泄漏。
Fix
将状态逻辑重构为使用 atomic selectors。通过强制组件只监听它们需要的特定布尔值或数组,UI 从“卡顿”变为约 60 fps 的流畅,即使在大量批处理时也是如此。
The Business Pivot: AdSense vs. Reality
我的最初计划是使用 Google AdSense。很快我发现 AdSense 讨厌 “单页工具”。它们会将其标记为 “低价值内容”,因为没有 2000 字的文章可供爬取。而且在一个标榜 “隐私” 的工具上放置 Google 跟踪器也显得很虚伪。
我意识到我的用户并不是在找随机的鞋子或信用卡,而是追求 数据安全。
Pivot
直接的联盟营销模式。我移除了占位广告位,设计了自定义的 “Native Cards”,推广注重隐私的工具(VPN、密码管理器)。这使收入模型与产品使命保持一致,感觉更像是技术栈推荐,而不是广告。
The Result
- 支持现代 Web 三位一体:JPG、PNG、WebP
- 完美处理矢量 SVG 文件
- 拥有 “Holy Grail” 对称布局,在实用性与变现之间取得平衡且不杂乱
- 坚守承诺:你可以打开页面,断开 Wi‑Fi,整天转换机密文档。任何内容都不会离开你的机器。
Live demo: Secure Converter
Source code: GitHub Repo