Page 1 of 1

【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!

Posted: 2024年 Jul 1日 16:20
by muhai

1. 背景

在照明等 Ray /原生小程序中,经常会有滑动条实时拖动并改变数值文本的需求,例如亮度滑动条上方的亮度数值展示:

image_20987886672929124.png

slider 组件是利用 sjs 直接操作 dom 改变滑块位置实现的,由于 ray 层走 setState、原生小程序走 setData 会多一层虚拟 dom /事件队列 更新周期,页面上其他使用 Ray 实现的部分会看起来“慢”于 slider 的滑动,导致数值展示卡顿。
如下图所示:

虚拟 DOM 更新机制:目前涂鸦小程序有两种开发方式,原生小程序开发和 Ray 开发,其更新机制不同,原生小程序将视图层和逻辑层解耦,通过事件系统进行通信,例如调用 setData 后会在逻辑层进行计算,然后发送事件到视图层通知视图更新;Ray 开发的小程序是使用了 React Diff 算法,走 React 内置的 reconciler 配置了自定义渲染器,桥接到小程序层,例如调用 setState 会在 React Diff 算法完成后,通过自定义渲染器将更新数据应用到小程序逻辑层渲染,即将 React 的 setState 翻译为小程序的 setData 。这两种方式都为异步渲染,需要等待执行环境空闲或合适的时机才会执行。

image_04489742775338601.gif

滑动结束后,微亮数值还在变化,因为 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>
  1. 这里先使用 sjs 标签引入了 index.sjs 代码,稍后编写。
  2. 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 效果对比

序号更新类型图示
1Ray 层更新,setState
image_04489742775338601.gif
2sjs 层直接操作 dom
image_10136265170537362.gif

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: 拖动值 }

Re: 【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!

Posted: 2024年 Jul 1日 18:36
by silverlight

感谢大佬的干货


Re: 【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!

Posted: 2024年 Jul 2日 20:57
by 幽冥墨

感谢分享!!!


Re: 【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!

Posted: 2024年 Jul 3日 10:19
by tdeveloper

👍🏻


Re: 【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!

Posted: 2024年 Jul 3日 10:22
by 智能小程序开发者

码住了!


Re: 【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!

Posted: 2024年 Jul 9日 17:27
by lshinylee

🆙🆙🆙


Re: 【技术干货】优化 Slider 数值渲染 ~ 使用 EventChannel + CSS3 实现高性能文本组件!

Posted: 2024年 Jul 25日 09:45
by MwM-Mai

ray开发中 @ray-js/components-ty-slider 是哪个版本, 为什么我装0.2.46 版本 moveEventName 没有这个属性