【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!
1. 背景
在照明等 Ray /原生小程序中,经常会有滑动条实时拖动并改变数值文本的需求,例如亮度滑动条上方的亮度数值展示:
slider 组件是利用 sjs 直接操作 dom 改变滑块位置实现的,由于 ray 层走 setState、原生小程序走 setData 会多一层虚拟 dom /事件队列 更新周期,页面上其他使用 Ray 实现的部分会看起来“慢”于 slider 的滑动,导致数值展示卡顿。
如下图所示:
虚拟 DOM 更新机制:目前涂鸦小程序有两种开发方式,原生小程序开发和 Ray 开发,其更新机制不同,原生小程序将视图层和逻辑层解耦,通过事件系统进行通信,例如调用 setData 后会在逻辑层进行计算,然后发送事件到视图层通知视图更新;Ray 开发的小程序是使用了 React Diff 算法,走 React 内置的 reconciler 配置了自定义渲染器,桥接到小程序层,例如调用 setState 会在 React Diff 算法完成后,通过自定义渲染器将更新数据应用到小程序逻辑层渲染,即将 React 的 setState 翻译为小程序的 setData 。这两种方式都为异步渲染,需要等待执行环境空闲或合适的时机才会执行。
滑动结束后,微亮数值还在变化,因为 sjs 层 dom 操作更新逻辑已经结束,Ray 逻辑层还在更新。
为解决这类问题,小程序框架推出了 eventChannel ,可实现从一个 rjs/sjs 组件发送消息到另一个 rjs/sjs 组件,利用这个能力,可以将数值文本也采用 sjs 实现,并通过 eventChannel 来同步 slider 组件中发出的值。
2. 技术原理
2.1 slider 组件发出 eventChannel
首先需要在 slider 组件的 start、move、end 事件中,增加 eventChannel 事件发送,伪代码如下:
slider.sjs 文件,以 move 事件为例:
Code: Select all
function onMove() {
const eventData = { value }
ownerInstance.triggerEvent('move', eventData); // 触发props.onStart
ownerInstance.eventChannel.emit('sliderMove', eventData) // 发送 eventChannel 事件
}
这样在 slider 组件拖动滑块时,就会持续发出 sliderMove 的 eventChannel 事件
2.2 使用 CSS3 实现数值文本组件
为了展示数值,需要编写一个文本组件,并且需要使用 sjs 代码实现,因为 eventChannel 只能在 rjs/sjs 中使用,rjs 是 canvas 组件专用,所以这里使用 sjs。
index.tyml文件:
Code: Select all
<sjs src="./index.sjs" module="computed"></sjs>
<text
id="{{instanceId}}"
instanceId="{{instanceId}}"
change:instanceId="{{computed.init}}"
class="text"
</text>
- 这里先使用 sjs 标签引入了 index.sjs 代码,稍后编写。
- text 标签设置了 id 属性,方便在 sjs 中 query 获取到 dom 元素,instanceId 和 change:instanceId 用来监听 instanceId 变化和执行 sjs 的 init 方法。
因为目前 sjs 还不支持修改 dom 的 textContent 属性,所以需要借助 CSS3 的 couter 属性实现通过 setStyle 来设置数值文本内容!
了解 counter-reset 属性用法,点击查看介绍
编写 css 代码,index.less 文件:
Code: Select all
.text {
counter-reset: perf-text-counter var(--perf-text-value);
}
.text::after {
content: counter(perf-text-counter);
}
在 text 标签加了 ::after 伪元素,通过伪元素的 content 属性展示数值文本。
我们需要在 sjs 中通过 setStyle 设置一个 css 变量应用过来,将滑动条的值作为 css 变量的值,但是伪元素的 content 属性不能直接使用 var 变量,这里借助了 css3的 counter 属性,在标签上通过 counter-reset 定义了一个 perf-text-counter ,值为 var(--perf-text-value) ,表示 perf-text-counter 这个计数器的值取自 css 变量 --perf-text-value,然后我们在 sjs 代码中通过 setStyle 设置滑动数值到 --perf-text-value 这个变量即可!
index.sjs 代码如下:
Code: Select all
const queryComponent = (ownerInstance, instanceId, selector) => {
const root = ownerInstance.selectComponent(`#${instanceId}`);
return selector ? root.selectComponent(selector) : root;
};
module.exports = {
init(newVal, oldVal, ownerInstance) {
const instanceId = newVal
const textNode = ownerInstance.selectComponent(`#${instanceId}`);
if (textNode) {
// 监听 slider 发出的 eventChannel 事件
ownerInstance.eventChannel.on(
'sliderMove',
res => {
// 设置 css 变量的值为滑动条的值
textNode.setStyle({
'--perf-text-value': `${res.value}`
});
},
);
}
},
};
这样就实现了监听 slider 中的拖动值,并直接操作 dom 设置到页面上,不会走 ray 虚拟 dom 更新周期。
2.2 demo 代码
最终实际使用如下:
Code: Select all
import Slider from '@ray-js/components-ty-slider';
import PerfText from '@ray-js/components-ty-perf-text';
const App = () => {
return (
<>
<PerfText
instanceId="test123"
eventName="sliderMove"
defaultValue="100"
style={{
fontSize: '24px',
color: 'green',
}}
/>
<Slider moveEventName="sliderMove" />
</>
);
};
支持了设置 eventName 、defaultValue、style,如果页面上使用了多个 PerfText 组件,可以使用不同的 eventName 区分开。
2.3 效果对比
序号 | 更新类型 | 图示 |
---|---|---|
1 | Ray 层更新,setState | |
2 | sjs 层直接操作 dom |
2.4 接入规范
安装 PerfText 组件:
Code: Select all
yarn add @ray-js/components-ty-perf-text
PerfText 基于 eventChannel 和组件逻辑解耦,事件对象的结构如下:
属性名 | 说明 |
---|---|
value | 用于 PerfText 渲染的值,必须为整数数字或整数字符串 /\d+$/ ,暂不支持小数 |
PerfText 数据接收方式如下:
Code: Select all
// PerfText
eventChannel.on(eventName, res => {
res.value // 接收事件中的value值做UI渲染
})
对接组件发送格式需如下:
Code: Select all
// 你的 rjs / sjs 组件,发送 value 值
eventChannel.emit(eventName, { value: 1234 })
通过 eventName 将你的组件数据发送到 PerfText 渲染。
示例:
Code: Select all
<PerfText eventName="sliderMove" />
<Slider moveEventName="sliderMove" /> // move 时发送 { value: 拖动值 }