2026年10月28日
同一张地图,三种方式:SVG、Rust 和 Flutter

EVnSteven 已经达到了一个舒适的状态。平台稳定,不需要太多维护,这正是我们一开始想要的:一个安静地持续运行的小系统。这留出了一些实验的空间,也让我们的创始人有时间启动其他几个项目,你可以在结尾处找到它们。
所以我们做了一个实验。我们将主页上的地图重建了两次:一次用 Rust 编译成 WebAssembly,另一次用 Flutter。本文将这三种版本放在同一页面进行比较。
1. 原版:SVG、CSS 和少量 JavaScript
简单来说: 这就是我们主页上的地图。它的构建方式和网页的其他部分一样:由浏览器已知如何显示和动画的形状组成的绘图。它是三者中最简单的,也是大多数访客看到的版本。
Ready Charging Check in Check out
它是一个 SVG 绘图。浏览器通过 CSS 运行大部分动画,剩下的由一个小脚本处理。
优点是:没有构建步骤,只需一个 Hugo 模板即可。它是矢量图,因此在任何尺寸下都保持清晰。所有 44 个点都可以通过键盘访问,每个点都有屏幕阅读器标签。少量文字已翻译成我们所有的语言。
代价是页面体积较大。地图的标记约为 107 KB(压缩后约 30 KB),运行时约有 670 个元素和约 200 个 CSS 动画。逻辑也难以测试:时间控制存在于 CSS 关键帧和页面中的脚本里,因此唯一的测试方式就是观察它。
2. Rust 与 WebAssembly
简单来说: Rust 是一种以速度快且能在程序运行前捕捉许多错误而闻名的编程语言。这里我们用 Rust 编写了地图的“核心”,将其打包成浏览器可运行的紧凑形式(称为 WebAssembly),让它自己绘制整个地图,就像画家每秒多次重绘同一画布一样。对你来说看起来是一样的。
整个地图由编译成 WebAssembly 的 Rust 在单个画布上绘制。Rust 代码掌控地图的所有行为,并且每一帧都重新绘制所有内容。
我们喜欢的部分是逻辑完全是纯 Rust,没有任何浏览器相关的内容:计算元素位置和变化时机的数学,以及读取陆地轮廓的解析器。它有 13 个测试,运行时间不到一秒,如果循环的时序不对,测试会失败,而不是让人注意到画面看起来怪怪的。大约三分之一的约 1,070 行 Rust 代码是经过测试的核心(包含测试);剩下的负责绘制画布和处理鼠标事件。
页面最终非常轻量:只有一个 canvas 元素。但下载体积较大。编译后的模块约 254 KB(压缩后约 92 KB),加上构建工具生成的约 28 KB JavaScript 粘合代码。使用时我们也感受到一些成本:
- 工具链。 构建需要 Rust、WebAssembly 目标以及一个命令行工具,其版本必须与库完全匹配。我们的构建脚本首先运行测试,如果有一个失败则停止。
- 无法通过键盘或屏幕阅读器访问单个城市。 画布就是一张图片。SVG 版本中的 44 个可聚焦点消失了。
- 仅支持英语。 标签写入程序中。
- 模糊。 软阴影边缘使用画布模糊滤镜,但并非所有浏览器都支持。我们没有测试 Safari,那里边缘可能是硬的。
3. Flutter
简单来说: Flutter 是 Google 提供的一个工具包,用于从一套代码构建可在手机、电脑和网页上运行的应用。它不使用网页自身的构建块,而是自己绘制所有内容。这里的地图是一个小应用,显示在此页面内,使用的是驱动我们充电计算器的同一工具包。
它用自定义绘制器绘制地图,并在这里以框架形式展示。Flutter 还可以用同一套代码构建手机和桌面应用,这是它的主要优势:写一次地图,多个地方运行。
这与 Rust 的 map 逻辑相同,移植到 Dart,包含 11 个测试。包括测试在内约有 1,100 行代码。Flutter 将每个城市作为带标签的按钮交给屏幕阅读器,而画布版本缺少这一点(我们还没有检查它在真实屏幕阅读器上的表现如何)。它也是下载量最大的:应用约为 1.8 MB(压缩后约 535 KB)。Flutter 的绘图引擎是额外下载的,但网站已经与计算器共享了一个副本,因此打开过计算器的访客不会再次下载。其文本使用网站的字体 Merriweather,浏览器在运行时获取。
我们发现了什么
| SVG + CSS + JS | Rust + WebAssembly | Flutter | |
|---|---|---|---|
| 地图下载大小 | 约 107 KB,内嵌页面 | 约 254 KB 模块 + 28 KB 粘合代码 | 约 1.8 MB 应用(绘图引擎共享) |
| 页面中的元素 | 约 670 个,含约 200 个 CSS 动画 | 1 个画布 | 1 个框架 |
| 逻辑测试 | 无 | 13 个,本地 | 11 个,本地 |
| 键盘可访问城市数 | 是,全部 44 个 | 否 | 向屏幕阅读器暴露(未测试) |
| 是否翻译 | 是 | 否,仅英文 | 否,仅英文 |
| 在任意缩放下保持清晰 | 是(矢量) | 根据屏幕像素密度重绘 | 根据屏幕像素密度重绘 |
| 构建所需 | 无 | Rust 工具链,匹配工具版本 | Flutter SDK |
关于速度的补充说明。在我们的测试中,SVG 地图和 Rust 地图均以屏幕刷新率运行(测试显示器约为 120 帧每秒),因此帧率无法区分它们。我们没有测量 Flutter 的帧率,也没有测量电池或 CPU 使用情况,因此无法判断哪一个对手机更省电。
令我们惊讶的事情
有些细节很容易出错。 我们的第一个克隆版本在地图上以固定比例绘制钥匙和时钟,因此在小屏幕上它们会缩小,看起来比原版小得多。原版使用页面自身的字体单位(rem,会随屏幕宽度变化)来设置大小。克隆版本现在从页面读取该大小。时钟也有所不同:原版的指针以15分钟为步进移动,而Rust和Flutter版本则是连续转动的。
时区未内置。 无论是编译成WebAssembly的Rust还是Dart,都没有携带包含“Asia/Dubai”等时区信息的数据库。它们都通过浏览器内置的Intl服务来获取时区信息,该服务还能正确处理夏令时。
三者共享输入。 Rust和Flutter地图从它们所在的页面读取城市列表和陆地轮廓数据,这与SVG地图使用的数据相同。修改一个城市,三者都会同步变化。这也意味着这些地图只能在提供这些数据的页面上运行。
Flutter web会记忆。 Flutter web安装了自己的缓存,因此在重建后我们一直看到旧版本,直到清除缓存。
我们的结论
SVG地图保留在首页。对于这样大小的绘图,它是最轻量的,易于访问,支持翻译,并且不需要构建。Rust是更有趣的亚军:如果我们需要地图逻辑可被证明是正确的,或者需要在浏览器中进行大量计算,经过测试的Rust核心是一个不错的选择。如果地图需要嵌入Flutter应用中,Flutter则很有意义。
一个实验不必改变产品才有价值。我们最终对每种方法的成本有了更清晰的认识,得到了经过测试的地图行为描述,并且有一个页面可以让你亲自查看这三种版本。
我们的创始人接下来正在打造的项目
一个稳定的平台为其他想法留下了时间。除了EVnSteven,我们的创始人还在开发这些项目:
非营利,开源
StrayCats (opens in a new tab)
David发起的一个项目,旨在帮助照顾街猫、流浪猫或任何猫的人,即使那只猫恰好是一只狗。项目仍处于构想阶段,尚无应用程序,David非常欢迎您的想法和反馈。献给Saibo,2010–2026。
现金跟踪,免费,即将上线
Effectivo (opens in a new tab)
带有创新的非完美记账,适合使用现金的人。只需几次点击即可记录购买,统计钱包中的现金,并回顾哪些支出值得。创新之处在于,每个持续使用现金的人都帮助保持现金流通,惠及所有人。免费且无广告,支持Android和iPhone,现已推出测试版和网页版演示。
