小程序采用双线程架构,渲染线程(UI 线程)和逻辑线程(JS 线程)分离。JS 线程不会影响 UI 线程的动画表现,如滚动效果。但引入的问题是,UI 线程的事件发生后,需跨线程传递到 JS 线程,进而触发开发者回调,当做交互动画(如拖动元素)时,这种异步性会带来较大的延迟和不稳定。worklet
动画正是为解决这类问题而诞生的,使得小程序可以做到类原生动画般的体验,有一点需要注意,worklet
动画相关接口仅在 Skyline 渲染模式下才能使用。
下面直接举例一个简单的循环动画效果来展示下worklet
动画的便捷性,封装了一个Animation-View的组件:
const { shared, Easing, timing, runOnUI, runOnJS, repeat } = wx.worklet
Component({
data: {
},
methods: {
startMoveAnimation() {
console.log('开始MoveA动画')
this._offset.value = repeat(timing(300,{
duration: 1000,
easing: Easing.ease
}), -1, true);
},
startScaleAnimation() {
this._scale.value = repeat(timing(1.5,{
duration: 500,
easing: Easing.ease
}), -1, true);
},
},
ready() {
let that = this
setTimeout(() => {
console.log('ready了 开始动画')
that.startMoveAnimation();
that.startScaleAnimation();
}, 2000);
const offset = shared(0)
this.applyAnimatedStyle('#moved-box', () => {
'worklet'
return {
transform: `translateX(${offset.value}px)`
}
})
this._offset = offset
const scale = shared(1)
this.applyAnimatedStyle('#scale-box', () => {
'worklet'
return {
transform: `scale(${scale.value})`
}
})
this._scale = scale
}
});
下面是wxml文件:
<view class="container">
<view id="moved-box" class="sliding-view"></view>
<view id="scale-box" class="scale-view"></view>
</view>
下面是wxss文件:
.container {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.sliding-view {
width: 100px; /* 视图的宽度 */
height: 120px; /* 视图的高度 */
background-color: blue; /* 视图的背景色 */
}
.scale-view {
margin-left: 60px;
width: 200px; /* 视图的宽度 */
height: 200px; /* 视图的高度 */
background-color: red; /* 视图的背景色 */
}
我们在组件的ready中给2个小组件声明了移动和缩放的worklet
动画,然后延迟2秒后执行,最外层是一个repeat
,次数传的-1
,代表这会一直循环执行动画。用起来的确非常的方便。
概念一:worklet
函数
一种声明在开发者代码中,可运行在 JS
线程或 UI
线程的函数,函数体顶部有 'worklet'
指令声明。
worklet 函数定义
function someWorklet(greeting) {
'worklet';
console.log(greeting);
}
// 运行在 JS 线程
someWorklet('hello') // print: hello
// 运行在 UI 线程
wx.worklet.runOnUI(someWorklet)('hello') // print: [ui] hello
worklet 函数间相互调用
const name = 'skyline'
function anotherWorklet() {
'worklet';
return 'hello ' + name;
}
// worklet 函数间可互相调用
function someWorklet() {
'worklet';
const greeting = anotherWorklet();
console.log('another worklet says ', greeting);
}
wx.worklet.runOnUI(someWorklet)() // print: [ui] another worklet says hello skyline
从 UI 线程调回到 JS 线程
function someFunc(greeting) {
console.log('hello', greeting);
}
function someWorklet() {
'worklet'
// 访问非 worklet 函数时,需使用 runOnJS
// someFunc 运行在 JS 线程
runOnJS(someFunc)('skyline')
}
wx.worklet.runOnUI(someWorklet)() // print: hello skyline
概念二:共享变量
在 JS
线程创建,可在两个线程间同步的变量。
const { shared, runOnUI } = wx.worklet
const offset = shared(0)
function someWorklet() {
'worklet'
console.log(offset.value) // print: 1
// 在 UI 线程修改
offset.value = 2
console.log(offset.value) // print: 2
}
// 在 JS 线程修改
offset.value = 1
runOnUI(someWorklet)()
由 shared
函数创建的变量,我们称为 sharedValue
共享变量。用法上可类比 vue3
中的 ref
,对它的读写都需要通过 .value
属性,但需注意的是它们并不是一个概念。sharedValue
的用途主要如下。
跨线程共享数据
由 worklet
函数捕获的外部变量,实际上会被序列化后生成在 UI
线程的拷贝,如下代码中, someWorklet
捕获了 obj
变量,尽管我们修改了 obj
的 name
属性,但在 someWorklet
声明的位置,obj
已经被序列化发送到了 UI
线程,因此后续的修改是无法同步的。
const obj = { name: 'skyline'}
function someWorklet() {
'worklet'
console.log(obj.name) // 输出的仍旧是 skyline
}
obj.name = 'change name'
wx.worklet.runOnUI(someWorklet)()
sharedValue
就是用来在线程间同步状态变化的变量。
const { shared, runOnUI } = wx.worklet
const offset = shared(0)
function someWorklet() {
'worklet'
console.log(offset.value) // 输出的是新值 1
}
offset.value = 1
runOnUI(someWorklet)()
驱动动画
worklet
函数和共享变量就是用来解决交互动画问题的。相关接口 applyAnimatedStyle
可通过页面/组件实例访问,接口文档参考。
<view id="moved-box"></view>
<view id="btn" bind:tap="tap">点击驱动小球移动</view>
Page({
onLoad() {
const offset = wx.worklet.shared(0)
this.applyAnimatedStyle('#moved-box', () => {
'worklet';
return {
transform: `translateX(${offset.value}px)`
}
})
this._offset = offset
},
tap() {
// 点击时修改 sharedValue 值,驱动小球移动
this._offset.value = Math.random()
}
})
当点击按钮 #btn
时,我们用随机数给 offset
进行赋值,小球会随之移动。
applyAnimatedStyle
接口的第二个参数 updater
为一个 worklet
函数,其捕获了共享变量 offset
,当 offset
的值变化时,updater
会重新执行,并将返回的新 styleObject
应用到选中节点上。
网友评论