# worklet animation

Mini Program uses a two-threaded architecture, the rendering thread (UI Threads) and logical threads (JS Threads) separated.JS Threads do not affect the UI An animated representation of a thread, such as a scrolling effect. But the problem with the introduction,UI After the event of a thread occurs, it needs to be passed across threads to JS Thread, which in turn triggers developer callbacks as interactive animations (such as dragging elements), this asynchrony causes significant latency and instability.

worklet Animation is born to solve such problems, so that the Mini Program can achieve a similar experience of native animation.

# Experience immediately

use worklet Animation ability when ensuring the following two:

  • Make sure the top right corner of the developer tools > details > In the local settings. will JS Compiled into ES5 The option is checked on (The code package volume increases slightly)
  • worklet The animation-related interface is available only in Skyline Can be used in rendering mode

First, we need to understand some related concepts.

# Concept 1:worklet function

A declaration in developer code that can be run on the JS Thread or UI Thread's function, the top of the function body has 'worklet' Directive statement.

# worklet Function definition

function someWorklet(greeting) {
  'worklet'
  console.log(greeting)
}

// Running in the JS Thread
someWorklet('hello') // print: hello

// Running in the UI Thread
wx.worklet.runOnUI(someWorklet)('hello') // print: [ui] hello

# worklet Call each other function

const Name = Skyline 

function anotherWorklet() {
  'worklet'
  return 'hello ' + Name
}

// worklet Functions can call each other
function someWorklet() {
  'worklet'
  const greeting = anotherWorklet()
  console.log('another worklet says ', greeting)
}

wx.worklet.runOnUI(someWorklet)() // print: [ui] another worklet says hello skyline

# from UI Thread back to the JS Thread

function someFunc(greeting) {
  console.log('hello', greeting)
}

function someWorklet() {
  'worklet'
  // Access to non worklet Function, you need to use the runOnJS
  // someFunc Running in the JS Thread
  runOnJS(someFunc)(Skyline )
}

wx.worklet.runOnUI(someWorklet)() // print: hello skyline

# Concept 2: Shared variables

in JS Thread creates variables that can be synchronized between two threads.

const { shared, RunOnUI } = wx.worklet

const offset = shared(0)
function someWorklet() {
  'worklet'
  console.log(offset.value) // print: 1
  // in UI Thread modification
  offset.value = 2
  console.log(offset.value) // print: 2
}
// in JS Thread modification
offset.value = 1

RunOnUI(someWorklet)()

by shared The variable created by the function, which we call sharedValue Shared variables. Analogy in usage Vue3 to hit the target ref, read and write to it need to be passed .value Properties, but it is important to note that they are not a concept.sharedValue The main uses are as follows.

# Shared data across threads

by worklet The external variables captured by the function are actually serialized and generated in the UI A copy of the thread, in the following code, someWorklet Captured the obj Variables, even though we modified the obj of Name Properties, but in the someWorklet Position of the statement,obj Has been serialized and sent to the UI Thread, so subsequent modifications cannot be synchronized.

const obj = { name: skyline} 
function someWorklet() {
  'worklet'
  console.log(obj.name) // The output is still skyline
}
obj.name = 'change name'

wx.worklet.runOnUI(someWorklet)() 

sharedValue Is used to synchronize the change of state between threads.

const { shared, RunOnUI } = wx.worklet

const offset = shared(0)
function someWorklet() {
  'worklet'
  console.log(offset.value) // The output is a new value. 1
}
offset.value = 1

RunOnUI(someWorklet)() 

# Drive animation

worklet Functions and shared variables are used to solve the interactive animation problem. Related interface applyAnimatedStyle Available through the page/Component Instance Access,[Interface Documentation Reference](https://developers.weixin.qq.com/miniprogram/dev/Reference/api/Component.html#ApplyAnimatedStyle- Parameter Definitions)。

<view id="moved-box"></view>
<view id="btn" bind:tap="tap" >Click to drive the ball to move</view>
Page({
  onLoad() {
    const offset = wx.worklet.shared(0)
    this.applyAnimatedStyle('#moved-box', () => {
      'worklet'
      return {
        transform: `translateX(${offset.value}px)`
      }
    })
    this._offset = offset
  },
  tap() {
    // Modify when clicked sharedValue Value, driving the ball to move
    this._offset.value = Math.random()
  }
})

When the button is clicked #Btn When we use random numbers to offset And the ball moves.

applyAnimatedStyle The second parameter of the interface updater For a worklet Function that captures the shared variable offset, When offset When the value of theupdater Will be re-executed and will return the new styleObject Applied to the selected node.

Of course, just look at this example, with the setData It doesn't seem to make a difference. But when worklet Animation andgestureWhen combined, there is a qualitative change.

# Example usage

# Gesture processing

<pan-gesture-handler  onGestureEvent="handlepan">
  <view class="circle"></view>
</pan-gesture-handler >
Page({
  onLoad() {
    const offset = wx.worklet.shared(0)
    this.applyAnimatedStyle('.circle', () => {
      'worklet'
      return {
        transform: `translateX(${offset.value}px)`
      }
    })
    this._offset = offset
  },
  handlebar (possibly ) {
    'worklet'
    if (evt.state === GestureState.ACTIVE) {
      this._offset.value += if possible deltaX 
    }
  }
})

When the finger is circle When you move on a node, it produces a smooth drag effect.handlebar The callback trigger is UI Thread, at the same time we modified the offset The value of, will be in UI Thread to produce animation, there is no need to go back to the JS Thread.

See more sample code for gesture processing

# Custom Animation Curves

<view id="moved-box"></view>
<view id="btn" bind:tap="tap" >Click to drive the ball to move</view>
const { shared, Easing, Timing } = wx.worklet
Page({
  onLoad() {
    const offset = shared(0)
    this.applyAnimatedStyle('#moved-box', () => {
      'worklet'
      return {
        transform: `translateX(${offset.value}px)`
      }
    })
    this._offset = offset
  },
  tap() {
    /**
     * Target value 300
     * Animation duration 200ms
     * Animation curve Easing.ease
     */
    this._offset.value = Timing(300, {
      duration: 200,
      easing: Easing.ease
    })
  }
})

Built-in such as Timingspring Developers can customize animation curves, and can combine and repeat different animation types to form interlaced animation.

See More Different Animation Types Usage

See more sample code for a slow function

  • Basic type sharedderivedcancelAnimation

  • Tool function RunOnUIrunOnJS

  • Type of animation Timingspringdecay

  • Combination animation Sequencerepeatdelay

  • Slow function Easing

  • Page Instance Method [applyAnimatedStyle](https://developers.weixin.qq.com/miniprogram/dev/Reference/api/Component.html#ApplyAnimatedStyle- Parameter Definitions)、[clearAniamtedStyle](https://developers.weixin.qq.com/miniprogram/dev/Reference/api/Component.html#ClearAnimatedStyle- Parameter Definitions)

# Note

worklet There are some limitations on calling inside the function to be aware of

  1. page/Defined in the component instance worklet Type callback function, internal access wx The interface on the runOnJS Transferred back to JS Thread.
  2. worklet The external variable referenced by the function, the object type will be Object.freeze Frozen, which requires direct access to specific properties on the object.
<tap-gesture-handler onGestureEvent="handleTap">
  <view class="circle" >showModal</view>
</tap-gesture-handler>
const { runOnJS, Timing } = wx.worklet
Page({
  data: {
    msg: 'Skyline'
  },
  onLoad() {
    const toValue = 100
    const showModal = this.showModal.bind(this)
    Timing(toValue, { duration: 300 }, () => {
      'worklet'
      runOnJS(showModal)(msg)
    })
  },
  handleTap () {
    'worklet'

    // Very important!!!
    // const { msg } = this.data
    // By deconstructing this.data access MSG, at this time this.data Will be Object.freeze Freezing, can lead to setData Unable to take effect
    // And by this.data.msg It will not freeze. this.data
    const msg = `hello ${this.data.msg}`

    // Very important!!!
    // Page method Must be passed this.methodName.bind(this) access
    const showModal = this.showModal.bind(this)
    
    // Scenario 1: Return JS Thread
    runOnJS(showModal)(msg)

    // Scene 2: Animation Completion Callback JS Thread
    const toValue = 100
    Timing(toValue, { duration: 300 }, () => {
      'worklet'
      runOnJS(showModal)(msg)
    })

    // Scenario 3: Call others worklet function
    this.doSomething()
  },
  doSomething() {
    'worklet'
  },
  showModal(msg) {
    wx.showModal({
      title: msg // title: hello skyline
    })
  },
})