--- lang: zh-CN --- # UI 优化指南 多数情况下,我们编写的简单 UI 并不需要特别的性能优化,渲染系统的懒更新及缓存机制已经可以有很优秀的性能表现,不过我们还是可能会遇到一些需要特殊优化的场景,本节将会讲述如何优化 UI 的性能表现,优化建议包括:避免元素平铺;使用 `Scroll` 或 `Page` 组件优化平铺性能;避免元素自我更新;使用 `cache` 和 `nocache` 标识;特殊场景禁用抗锯齿和高清;在合适场景下隐藏一些元素等。 ## 避免元素平铺 在不使用 `Scroll` 组件时,我们需要尽量避免元素平铺,因为这会导致更新时渲染次数上升,从而引起性能下降。我们建议善用树形结构的缓存特性,将可以作为一个整体的元素使用一个容器 `container` 包裹起来,减少更新时的渲染次数,尤其是对于那些不常更新的元素来说,更应该使用容器包裹。不过我们也不建议嵌套过深,这可能导致浪费在递归渲染上的时间过长,渲染效率变低。 画布渲染树的深度遍历特性使得: - 每个独立容器的更新会触发子树的重新渲染 - 容器层级过深会增加递归调用栈开销 - 合理分组可将高频/低频更新元素隔离 下面是代码示例: ```tsx // ❌ 差的写法,全部平铺在一个容器里 {/* 中间省略 998 个元素 */} // ✅ 好的写法 {/* 把不常更新的单独放到一个容器里面 */} {/* 中间省略 988 个元素 */} {/* 把常更新的单独放到一个容器里面 */} {/* 中间省略 8 个元素 */} ``` ## 使用 `Scroll` 或 `Page` 组件优化平铺性能 在一些特殊情况下,我们不得不使用平铺布局,例如上一节提到的怪物手册,或是展示一个列表等,这时候必须平铺元素。这时候我们可以使用 `Scroll` 组件或 `Page` 组件来优化性能表现。`Scroll` 组件中,只有在画面内的元素会被渲染,而画面外的不会被渲染,这会大大提高渲染效率;`Page` 组件允许你把列表拆分成多个部分,然后把内容放在不同页中,从而提高渲染性能。极端情况下,`Page` 组件的渲染效率要明显高于 `Scroll` 组件,但是滚动条对于交互更友好,我们推荐在简单场景下使用 `Scroll` 组件,而对于复杂场景,换为 `Page` 组件。两个组件的使用方式可以参考 [API 文档](../api/motajs-render-elements/)。 我们建议: 1. **优先使用 Scroll**: - 元素数量 < 500 - 需要流畅滚动交互 - 元素高度不固定 2. **切换至 Page**: - 元素数量 > 1000 - 需要支持快速跳转 - 存在复杂子组件(如嵌套动画) 下面是代码示例: ```tsx // ❌ 差的写法,全部平铺在一个容器里 {/* 中间省略 998 个元素 */} // ✅ 好的写法,使用 Scroll 组件优化 {/* 中间省略 998 个元素 */} // ✅ 好的写法,使用 Page 组件优化 {(page: number) => { return list.slice(page * 10, (page + 1) * 10).map(v => ) }} ``` ## 避免元素自我更新 元素自我更新是指,在元素的渲染函数内,触发了元素的冒泡更新,这会导致更新无限循环,而且难以察觉。为了解决难以察觉的问题,我们使用了一种方式来专门探测这种情况。常见的触发元素自我更新的场景就是使用 `sprite` 元素,例如: ```tsx const element = ref(); const render = () => { element.value?.update(); }; ; ``` 在上面这段渲染代码中,`sprite` 元素的渲染函数又再次触发了自我更新,这会导致更新无限循环。在开发环境下,这种情况会在控制台抛出警告:`Unexpected recursive call of Sprite.update?uid in render function. Please ensure you have to do this, if you do, ignore this warn.`,这会告诉你是哪个类型的元素触发了循环更新,以及对应元素的 `uid`,从而帮助你寻找问题所在。不过,样板还是留出了一个口子,如果你必须使用循环更新,那么你可以忽略此条警告,在网站上游玩时这条警告将不会被触发,游戏会正常运行。 ## 使用 `cache` 和 `nocache` 标识 `cache` 和 `nocache` 表示可以让你更加精确地控制渲染树的缓存行为,从而更好地优化渲染性能。默认情况下,这些元素是会被缓存的:`container` `container-custom` `template` `sprite` `image` `icon` `layer` `layer-group` `animation`,对于这些元素,你可以使用 `nocache` 标识来禁用它们的缓存,对于其本身或其子元素的渲染较为简单的场景,禁用缓存后渲染效率可能会更高。其他元素默认是禁用缓存的,如果你的渲染内容比较复杂,例如 `g-path` 元素的路径很复杂,可以使用 `cache` 表示来启用缓存,从而提高渲染效率。示例代码如下: ```tsx const render = (canvas: MotaOffscreenCanvas2D) => { canvas.ctx.fillRect(0, 0, 200, 200); }; // ❌ 差的写法,一个简单的矩形绘制,但是 sprite 默认启用缓存,可能会拉低渲染效率 ; // ✅ 好的写法,使用 nocache 标识禁用 sprite 的缓存机制 ; ``` ## 特殊场景禁用抗锯齿和高清 默认情况下,大部分元素都是默认启用高清即抗锯齿的(`layer` 和 `layer-group` `icon` 不启用),这可能会导致一些不必要的计算出现,从而拉低渲染性能。对于一些需要保持像素风的内容,我们建议关闭抗锯齿和高清画布。代码示例如下: ```tsx // ❌ 差的写法,像素风图片使用默认设置,启用了抗锯齿和高清 // ✅ 好的写法,关闭了默认的抗锯齿和高清 ``` ## 在合适场景下隐藏一些元素 如果一个元素在某些场景下需要隐藏,另一些场景下需要显示,我们建议使用 `hidden` 属性来设置,而不是通过把它移动到画面外、调成透明颜色、使用 `if` 或三元表达式判断等方式。示例代码如下: ```tsx // ❌ 差的写法,使用条件表达式切换元素显示与否 { !hidden.value && ; } // ✅ 好的写法,使用 hidden 属性