---
title: Render
---

# RenderScript

**Rendering script** can be used to handle high-frequency drawing requirements and improve the animation rendering performance of the view. The file name suffix is `.rjs`, and provides the `Render()` function to declare a rendering script module for use in combination with pages or components. Main application scenarios: canvas chart rendering, webGL graphics rendering, etc.

- Rendering function registration `Render()` must be called in `*.rjs`. It must be called and can only be called once. Otherwise, unexpected effects will occur.
- The rendering script only provides `api` to operate on `canvas`.
- In a smart applet, you can draw `canvas` through `ty.createCanvasContext`, but this operation requires communication from the logic layer to the view layer. If you need to draw to the `canvas` frequently, a render script may be more suitable for you.
- The rendering script environment has an independent execution environment. Global objects such as `window` `document` `localStorage` will be an empty object. You can adapt the global objects according to the actual situation.
- If you need to use it in `Ray`, please check [Ray RJS](/en/miniapp/develop/ray/framework/render)

> This feature requires Tuya MiniApp IDE version greater than **0.3.0**

## Instance object

- `instance`

  - `callMethod(name: string, ...args: any[]): void`: Call the method in the associated page or component instance.
  - `getCanvasById(id: string): Promise<HTMLCanvas | null>` obtains the `canvas` object through `canvas id`. Returns a [Canvas](/en/miniapp/develop/miniapp/api/canvas/RJS-Canvas/Canvas) object.

  ```js
  // index.rjs
  export default Render({
    init(id) {
      this.instance.getCanvasById(id).then((canvas) => {
        // Get the canvas node with the ID in the page
      });
    },
  });
  ```

  - `getSystemInfo(): SystemInfo` Gets system-related information. Returns an object with the following contents.

  | Variable name | Remarks                                        | Type   |
  |---------------|------------------------------------------------|--------|
  | screenWidth   | screen width                                   | number |
  | screenHeight  | screen height                                  | number |
  | navbarHeight  | Top navigation bar height                      | number |
  | tabbarHeight  | Bottom tab bar height                          | number |
  | platform      | device type: android/ios                       | string |
  | statusHeight  | status bar height                              | number |
  | pixelRatio    | pixel ratio                                    | number |
  | orientation   | screen status (horizontal and vertical screen) | string |

  - `getBoundingClientRectById(id: string): Rect` Gets the relevant information of the corresponding node, including the following content.

  | variable name | type   |
  |---------------|--------|
  | left          | number |
  | right         | number |
  | top           | number |
  | bottom        | number |
  | width         | number |
  | height        | number |

  - `createWorker(path: string): Worker`: Create a worker object.

  | Method Name                             | Remarks                        |
  |-----------------------------------------|--------------------------------|
  | postMessage(...args: any[])             | Send messages to worker        |
  | onMessage(fn: (...args: any[]) => void) | Listen to messages from worker |
  | terminate(): void                       | Close the worker               |
  
  - `eventChannel`: eventChannel instance object, used for event communication between RJS or RJS and SJS. **Note that event names must be unique to avoid event conflicts. **

For example: `page1/index.rjs` and `page2/index.rjs` use `this.instance.eventChannel` to communicate

```js
  // page1: index.rjs
   export default Render({
     init(id) {
       this.instance.getCanvasById(id).then((canvas) => {
         // Get the canvas node with the ID in the page
         // ...
       });
       this.instance.eventChannel.emit('eventName', { data: +new Date });
     },
   });
```

```js
// page2: index.rjs
export default Render({
   foo(e){
     // ...
     console.log(e)
   },
   init(id) {
     this.instance.eventChannel.on('eventName', foo);
   },
   //Cancel monitoring when canvas is destroyed
   destroy(){
      this.instance.eventChannel.off('eventName', foo);
   }
});
```

## Graphics library plug-in

- [F2](https://antv-f2.gitee.io/en/api/f2)
- [ECharts](https://echarts.apache.org/en/index.html)
- [Three.js](https://threejs.org/)

Please see [plugin system](/en/miniapp/develop/miniapp/framework/plugin/intro) for usage instructions

## Things to note

### 1. Consistency

Similar to how `index.json` works for `index.tyss`, the rendering script serves a part of a page or component. You can use only one rendering script for a `page` or `component`.

- The rendering script cannot work for multiple page files or component files. Otherwise, the build might fail. To reuse the `rjs` file, you can abstract a `js` file to isolate the logic.

```js
// The build failed.
import MyRender from '../other/index.rjs';
Page({
  onLoad() {
    this.render = new MyRender(this);
  },
});
```

- When the rendering function is instantiated, it is associated with the page instance or component instance.

```js
import MyRender from './index.rjs';
Page({
  onLoad() {
    this.render = new MyRender(this);
  },
});
```

### 2. Independent operating environment

- The rendering script allows you to import other JavaScript modules or third-party toolkits. The rendering script runs in an independent operating environment. If the imported third-party library accesses an inaccessible object, you must modify the global objects to adapt to the third-party library.

### 3. Format of registering the rendering function

- In the `Render` function, you must declare an object literal as the request parameter. Do not pass in a variable.

```js
// This format is supported.
export default Render({ draw() {} });
// This format is not supported.
const config = { draw() {} };
export default Render(config);
```

- The `Render` function must be exported by default.

```js
export default Render({...})
// Or
module.exports = Render({...})
```

### 4. Interaction data serialization

- When you call the methods of the rendering script, the request parameters must be serialized data content.

```js
// index.js
import Render from './index.rjs';
Page({
  onLoad: function () {
    this.render = new Render(this);
  },
  onReady() {
    console.log(render.test({ fn: function () {} })); // fn cannot be serialized.
  },
});
```

- In the rendering script, you can use `this.instance.callMethod('method', 'arg1', 'arg2')` to call instance methods. `method` specifies the name of an instance method and `arg1` and `arg2` are the request parameters.
- When the logic layer calls `RJS`, an event is triggered. In this call, `return` is unused. Therefore, you must use `callMethod` to exchange data.

```js
// index.rjs
export default Render({
  test() {
    this.instance.callMethod('testRjs', 'text'); // Correct
    return 'test'; // Wrong
  },
});
```

```js
// index.js
import Render from './index.rjs';
Page({
  onLoad: function () {
    this.render = new Render(this);
  },
  testRjs: function (arg1) {
    console.log('RJS parameter received, serialized parameter only', arg1); // args = test
  },
  onReady() {
    console.log(render.test()); // The page instance cannot get the `return` data.
  },
});
```

### 5. Lifecycle of running canvas chart drawing tasks

You must implement `canvas` drawing in the `onReady` lifecycle of a page or the `ready` component of a component. Otherwise, if you implement drawing in other lifecycles, the returned node height might be incorrect and thus the drawing result has errors.

- Page

```js
// index.js
import Render from './index.rjs';
Page({
  /**
   * The lifecycle function to invoke when the page is loaded.
   */
  onLoad: function (query) {
    this.render = new Render(this);
  },
  onReady() {
    // Implements drawing in this lifecycle.
    this.render.draw();
  },
});
```

- Component

```js
// index.js
import Render from './index.rjs';
Component({
  lifetimes: {
    created: function () {
      this.render = new Render(this);
    },
    ready: function (e) {
      // Implements drawing in this lifecycle.
      this.render.draw();
    },
  },
});
```

### 6. Support Promise function return value

This method can simplify the cost of communicating with page/component instances and avoid frequent use of `this.instance.callMethod`

- Basic library `>= 2.21.0`
- Tuya MiniApp IDE `>= 0.7.1`

```js
import Render from './index.rjs';
Page({
  onLoad: async function (query) {
    this.render = new Render(this);
    const result = await this.render.sum(1, 2);
    // result = 3
  }
});
```

```js
// index.rjs
export default Render({
  async sum(a, b) {
    return a + b
  }
});
```

## Example

### 1. Draw canvas animation

```xml
<!-- index.tyml -->
<canvas
  type="2d"
  canvas-id="canvas"
></canvas>
```

```js
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * The lifecycle function to invoke when the page is loaded.
   */
  onLoad: function () {
    render = new Render(this);
  },

  onReady() {
    render.renderCar();
  },
});
```

```js
// index.rjs
export default Render({
  position: {
    x: 150,
    y: 150,
    vx: 2,
    vy: 2,
  },
  x: -100,

  async renderCar() {
    let canvasCar = await this.instance.getCanvasById('canvas');
    let width = canvasCar.width;
    let height = canvasCar.height;

    let ctx = canvasCar.getContext('2d');

    let dpr = 1;
    canvasCar.width = width * dpr;
    canvasCar.height = height * dpr;
    ctx.scale(dpr, dpr);

    let renderLoop = () => {
      this.render(canvasCar, ctx);
      canvasCar.requestAnimationFrame(renderLoop);
    };
    canvasCar.requestAnimationFrame(renderLoop);

    let img = canvas.createImage();
    img.onload = () => {
      this._img = img;
    };
    img.src = './car.png';
  },

  render(canvas, ctx) {
    ctx.clearRect(0, 0, 300, 300);
    this.drawBall(ctx);
    this.drawCar(ctx);
  },

  drawBall(ctx) {
    let p = this.position;
    p.x += p.vx;
    p.y += p.vy;
    if (p.x >= 300) {
      p.vx = -2;
    }
    if (p.x <= 7) {
      p.vx = 2;
    }
    if (p.y >= 300) {
      p.vy = -2;
    }
    if (p.y <= 7) {
      p.vy = 2;
    }

    function ball(x, y) {
      ctx.beginPath();
      ctx.arc(x, y, 5, 0, Math.PI * 2);
      ctx.fillStyle = '#1aad19';
      ctx.strokeStyle = 'rgba(1,1,1,0)';
      ctx.fill();
      ctx.stroke();
    }

    ball(p.x, 150);
    ball(150, p.y);
    ball(300 - p.x, 150);
    ball(150, 300 - p.y);
    ball(p.x, p.y);
    ball(300 - p.x, 300 - p.y);
    ball(p.x, 300 - p.y);
    ball(300 - p.x, p.y);
  },

  drawCar(ctx) {
    if (!this._img) return;
    if (this.x > 350) {
      this.x = -100;
    }
    ctx.drawImage(this._img, this.x++, 150 - 25, 100, 50);
    ctx.restore();
  },

  async renderImageData() {
    let canvas3 = await this.instance.getCanvasById('canvas3');
    let ctx = canvas3.getContext('2d');
    let imgData = ctx.createImageData(100, 100);
    for (i = 0; i < imgData.width * imgData.height * 4; i += 4) {
      imgData.data[i + 0] = 255;
      imgData.data[i + 1] = 0;
      imgData.data[i + 2] = 0;
      imgData.data[i + 3] = 155;
    }
    ctx.putImageData(imgData, 50, 50);
  },
});
```

### 2. Draw ImageData and Path2D

```xml
<!-- index.tyml -->
<canvas
  type="2d"
  canvas-id="canvas3"
  style="width: 300px; height: 300px;border: 1px solid orange"
></canvas>
<canvas
  type="2d"
  canvas-id="canvas4"
  style="width: 300px; height: 300px;border: 1px solid blue"
></canvas>
```

```js
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * The lifecycle function to invoke when the page is loaded.
   */
  onLoad: function (query) {
    render = new Render(this);
  },

  onReady() {
    render.renderImageData();
    render.renderPath2D();
  },
});
```

```js
// index.rjs
export default Render({
  async renderImageData() {
    let canvas3 = await this.instance.getCanvasById('canvas3');
    let ctx = canvas3.getContext('2d');
    let imgData = ctx.createImageData(100, 100);
    for (i = 0; i < imgData.width * imgData.height * 4; i += 4) {
      imgData.data[i + 0] = 255;
      imgData.data[i + 1] = 0;
      imgData.data[i + 2] = 0;
      imgData.data[i + 3] = 155;
    }
    ctx.putImageData(imgData, 50, 50);
  },

  async renderPath2D() {
    let canvas4 = await this.instance.getCanvasById('canvas4');
    let ctx = canvas4.getContext('2d');
    let path1 = canvas4.createPath2D();
    path1.rect(10, 10, 100, 100);
    let path2 = canvas4.createPath2D(path1);
    path2.moveTo(220, 60);
    path2.arc(170, 60, 50, 0, 2 * Math.PI);
    ctx.stroke(path2);
  },
});
```

### 3. Draw charts

```xml
<!-- tyml -->
<canvas canvas-id="f2" class="chart" />
<button bindtap="draw">Render Chart</button>
```

```js
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * The lifecycle function to invoke when the page is loaded.
   */
  onLoad: function () {
    render = new Render(this);
  },

  onReady() {
    this.draw();
  },

  draw() {
    render.draw([
      { genre: 'Sports', sold: Math.floor(Math.random() * 500) },
      { genre: 'Strategy', sold: Math.floor(Math.random() * 500) },
      { genre: 'Action', sold: Math.floor(Math.random() * 500) },
      { genre: 'Shooter', sold: Math.floor(Math.random() * 500) },
      { genre: 'Other', sold: Math.floor(Math.random() * 500) },
    ]);
  },
});
```

```js
// index.rjs
import F2 from '@antv/f2';
let chart;
export default Render({
  position: {
    x: 150,
    y: 150,
    vx: 2,
    vy: 2,
  },
  x: -100,
  async draw(data) {
    if (chart) {
      chart.clear(); // Clears data.
      chart.interval().position('genre*sold').color('genre');
      // Step 2: Load the data source.
      chart.source(data);
      // Step 4: Render a chart.
      chart.render();
    } else {
      let canvas = await this.instance.getCanvasById('f2');
      // Step 1: Create a Chart object.
      chart = new F2.Chart({
        el: canvas,
        pixelRatio: this.instance.getSystemInfo().pixelRatio || 2, // Specifies the pixel ratio.
      });

      // Step 2: Load the data source.
      chart.source(data);

      // Step 3: Create the graphic syntax and draw a histogram. The properties genre and sold determine the location of the histogram. genre is mapped to the x-axis and sold is mapped to the y-axis.
      chart.interval().position('genre*sold').color('genre');

      // Step 4: Render the chart.
      chart.render();
    }
  },
});
```

### 4. Draw Lottie animation and call methods at the logic layer.

- To use Lottie, you must import [lottie-miniapp](https://github.com/landn172/lottie-miniapp) in `index.rjs`.

```xml
<!-- tyml -->
<canvas type="2d" canvas-id="canvas5"></canvas>
```

```js
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * The lifecycle function to invoke when the page is loaded.
   */
  onLoad: function (query) {
    render = new Render(this);
  },
  animationPlay: function (arg1, arg2) {
    console.log('Draw animation', arg1, arg2);
  },
  onReady() {
    render.renderLottie();
  },
});
```

```js
// index.rjs
import { lottieData } from './lottie-data';
import lottie from 'lottie-miniapp';
export default Render({
  async renderLottie() {
    let canvas5 = await this.instance.getCanvasById('canvas5');
    let canvasContext = canvas5.getContext('2d');
    lottie.loadAnimation({
      renderer: 'canvas', // Only canvas is supported.
      loop: true,
      autoplay: true,
      animationData: lottieData,
      // path: animationPath,
      rendererSettings: {
        // Fill in canvas data.
        canvas: canvas5,
        context: canvasContext,
        clearCanvas: true,
      },
    });
    this.instance.callMethod('animationPlay', 'arg1', 'arg2');
  },
});
```
