近日,一项令人瞩目的创新在网络世界中掀起轩然大波:利用Canvaskit技术成功实现超级丝滑的《原神地图》。这一全新开源项目为游戏玩家提供了更流畅的使用体验,同时也展示了技术的潜力和创新的可能性。这不仅是对网页应用的革新,更是对Web技术的巨大突破,为未来的互联网发展描绘了令人振奋的画面。
canvaskit 版:
因为 gif 最高只有 50hz,实际上 cavnaskit 版要远比这看起来的更丝滑,你可以通过以下链接自行体验。
官方网页版:webstatic.mihoyo.com/ys/app/inte…
canvaskit 版:空荧酒馆原神地图 (gitee.io) (备用地址:ky-genshin-map.7c00.cc/ )
或者这个 demo:
为什么官方网页版的性能如此糟糕以及为什么 canvaskit 版能有如此高的性能?第一个问题,官方网页版地图引擎用的是 leaflet,这是一个以 dom 为主要实现方式的地图引擎,而频繁地大量操作 dom 会导致严重的性能问题。你可以想象一下,要保证视觉上流畅,手势及动画的采样频率至少是 60hz,意味着单个 dom 节点每秒就要变换 60 次,一旦数量超过 100 个,对浏览器来说就是无法承受的压力。
但是 leaflet 也有 canvas 实现的 layer,或者这么说,如果性能瓶颈在于 dom,那么用浏览器提供的 canvas api 就应该可以解决,为什么还需要 canvaskit 呢?
在回答这个问题之前,先介绍一下 canvaskit,这其实就是 skia 的 js + wasm 版,c++ 实现的渲染引擎被编译成了 wasm,通过 js 提供类似 canvas api 的绘制接口。也许你已经知道 chrome 的底层就是 skia 做渲染引擎,canvas api 也可以视为 skia 绘制接口的封装,那么 skia 编译成 wasm 再提供 js api 不是脱裤子放屁吗。
不是的,简单来说 canvas api 只提供一些简单的绘制接口,上限远低于提供了 skia 底层接口的 canvaskit,如果只是简单场景,canvas api 确实已经足够了,但在复杂场景,或者说渲染压力非常大的情况下,canvas api 很容易达到性能瓶颈,而 canvaskit 则可以更好地胜任。推荐阅读这篇文章 canvaskit-wasm —— 在浏览器中直接使用 skia 的能力渲染 sketch 文件 - 知乎 (zhihu.com) 里面有解释 canvaskit 对比 canvas api 的优势。
举一个原神地图里的例子,在需要渲染大量重复图片(标记物)的场景下,canvas api 只能大量调用 drawImage 一个个地绘制,而 canvaskit 提供了 drawAtlas 可以对图片按 transforms 批量绘制。根据我的实践,一帧内 drawImage 调用达到几百次就足以导致帧超时,而 drawAtlas 一次处理上万个 transforms 都没有什么压力。
丝滑的地图体验还不止于渲染性能——手势识别与动画渲染性能只是丝滑体验的基础,要做到真正的丝滑,符合直觉的动画反馈才是关键。道理很简单,和手机上的滑动滚动一样,当我们拖拽地图结束的时候,我们会期望地图以拖拽、缩放结束时的速度继续运动一段距离,并且速度的衰减应该符合现实的阻尼运动,这意味着不能简单套个 timing-function。当然也会有用到 timing-function 的时候,比如双击放大就适合用 timing-function 做动画。
现实是,很多原生地图并没有很重视动画反馈,要么没做,要么做了但实现的动画不符合直觉。尽管有不少地图 SDK 已经是用 webgl 做渲染,性能没有什么问题,但用起来仍然谈不上丝滑。所以我决定从地图引擎开始实现,尝试实现理想中丝滑的原神地图体验。
好在手势识别及动画都已经有不错的库可以直接使用,手势识别用的是 @use-gesture/vanilla,而动画用的是 popmotion 其中主要用 inertia 做阻尼动画。
手势识别及动画的核心任务修改 offset,及 scale,offset 是画布的偏移量,直观来说就是拖拽时的位移量,scale 是缩放系数,类似于 transform: scale()。
拖拽手势处理拖拽手势是最容易处理的,@use-gesture 已经把用到的数值都准备好了,比如 delta 是位移差值,velocity 是速度,direction 是运动方向,只要选好合适的参数就可以很容易实现符合直觉的阻尼动画。
如果是 MarkerLayer,要处理的情况要多一些,比如为了实现用 vue 组件作为 Marker image,需要先渲染出一个真实的 dom,然后把这个 dom 转成 image,再传入 MarkerLayer 去渲染。
最后从手势识别开始,到 canvaskit 实现地图引擎,再到 react/vue 组件封装,最后构建出可交互的地图应用。每个部分都还有不少细节值得介绍,碍于篇幅原因先做一个大致的介绍,以后再写文章继续介绍。