小程序开发框架 —— 框架接口汇总(五)

Render Script

渲染脚本,可用于处理高频的绘图需求,可以提高视图的动画渲染性能。文件名后缀为 .rjs, 提供 Render() 函数进行声明一个渲染脚本模块,与页面或组件结合使用。主要应用场景 canvas 图表渲染,webGL 图形渲染等。

  • 渲染函数注册 Render() 必须在 *.rjs 中调用,必须调用且只能调用一次。不然会出现无法预期的效果。
  • 渲染脚本仅提供 api 对 canvas 进行操作。
  • 在智能小程序中,您可以通过 ty.createCanvasContext 来绘制 canvas,但是该操作需要依赖逻辑层到视图层通信。如果您需要频繁地绘制 canvas,那么渲染脚本可能更适合您。
  • 渲染脚本环境有独立的执行环境, window document localStorage 等全局对象将会是一个空对象,您可以依据实际情况对全局对象进行适配。
  • 如果您需要在Ray中使用,请查看Ray RJS

此功能要求 Tuya MiniApp Tools 版本大于 0.3.0 eventChannel 要求基础库版本大于 2.18.0

实例对象

  • callMethod(name: string, ...args: any[]): 调用关联页面或组件实例中的方法。(旧实例方法,建议替换成instance实例对象下的callMethod方法。)

  • instance

    • callMethod(name: string, ...args: any[]): 调用关联页面或组件实例中的方法。
    • getCanvasById(id: string, instance) 通过canvas id获取canvas对象。返回一个Canvas对象。
    // index.rjs
    export default Render({
      init(id) {
        this.instance.getCanvasById(id).then((canvas) => {
          // 获取到页面中 ID 的 canvas 节点
        });
      },
    });
    • getSystemInfo() 获取系统相关信息。返回一个对象,包含以下内容。
    变量名备注类型
    screenWidth屏幕宽度number
    screenHeight屏幕高度number
    navbarHeight顶部导航栏高度number
    tabbarHeight底部 tab 栏高度number
    platform设备类型:android/iosstring
    statusHeight状态栏高度number
    pixelRatio像素比number
    orientation屏幕状态(横竖屏)string
    • getBoundingClientRectById(id: string, instance) 获取对应节点相关信息,包含以下内容。
    变量名类型
    leftnumber
    rightnumber
    topnumber
    bottomnumber
    widthnumber
    heightnumber
    • createWorker: 创建worker对象。
    • eventChannel: eventChannel实例对象,用来进行RJS间或RJS和SJS的事件通信。 需要注意 事件名唯一,避免事件冲突。 要求基础库版本大于 2.18.0
 // page1: index.rjs
  export default Render({
    init(id) {
      this.instance.getCanvasById(id).then((canvas) => {
        // 获取到页面中 ID 的 canvas 节点
        // ...
      });
      this.instance.eventChannel.emit('eventName', { data: +new Date  });
    },
  });
// page2: index.rjs
export default Render({
  foo(e){
    // ...
    console.log(e)
  },
  init(id) {
    this.instance.eventChannel.on('eventName', foo);
  },
  // 在canvas销毁时取消监听
  destory(){
     this.instance.eventChannel.off('eventName', foo);
  }
});

👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。  

图形库插件

使用方法请查看插件系统

注意事项

1. 一致性

渲染脚本作为页面 和 组件的一部分,等同于 index.json 与 index.tyss 的关系。一个 Page 或一个 Component 只有一个渲染脚本。

  • 禁止跨页面文件或组件文件使用渲染脚本,否则会构建失败。如需复用 rjs,可通过抽象 js 文件进行逻辑拆分。
// 构建失败
import MyRender from '../other/index.rjs';
Page({
  onLoad() {
    this.render = new MyRender(this);
  },
});
  • 渲染函数实例化时与页面实例或组件实例进行关联
import MyRender from './index.rjs';
Page({
  onLoad() {
    this.render = new MyRender(this);
  },
});

2. 独立运行环境

  • 渲染脚本支持引入其他 js 模块或三方工具包。渲染脚本有独立的执行环境,如果您引用的三方库使用访问了无法访问的对象,需要对该全局对象进行适配。

3. 渲染函数注册格式

  • Render 函数入参必须声明一个对象字面量,不可传入一个变量。
// 支持
export default Render({ draw() {} });
// 不支持
const config = { draw() {} };
export default Render(config);

  • 必须默认导出 Render 函数
export default Render({...})
// 或
module.exports = Render({...})

4. 交互数据可序列化

  • 执行渲染脚本中方法时,入参数据需为可序列化的数据内容。
// index.js
import Render from './index.rjs';
Page({
  onLoad: function () {
    this.render = new Render(this);
  },
  onReady() {
    console.log(render.test({ fn: function () {} })); // fn 不可序列化
  },
});

  • 在渲染脚本需要调用实例方法,可使用 this.instance.callMethod('method', 'arg1', 'arg2'),其中 method 是实例方法名,arg1arg2 是入参数据。
  • 逻辑层调用 RJS 是一个事件,因此并不存在 return,数据交互请使用 callMethod
// index.rjs
export default Render({
  test() {
    this.instance.callMethod('testRjs', 'text'); // 正确
    return 'test'; // 错误
  },
});
// index.js
import Render from './index.rjs';
Page({
  onLoad: function () {
    this.render = new Render(this);
  },
  testRjs: function (arg1) {
    console.log('接收到了RJS的参数, 仅支持可序列化参数', arg1); // args = test
  },
  onReady() {
    console.log(render.test()); // 页面实例无法直接获取 return 的数据
  },
});

5. 执行绘制 canvas 生命周期

在执行 canvas 绘制应在页面的 onReady 生命周期、或组件的 ready 生命周期,在其他生命周期中执行可能会出现 canvas 节点高度获取不正确,导致绘制异常的情况。

  • 页面
// index.js
import Render from './index.rjs';
Page({
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (query) {
    this.render = new Render(this);
  },
  onReady() {
    // 在此生命周期执行绘制
    this.render.draw();
  },
});

  • 组件
// index.js
import Render from './index.rjs';
Component({
  lifetimes: {
    created: function () {
      this.render = new Render(this);
    },
    ready: function (e) {
      // 在此生命周期执行绘制
      this.render.draw();
    },
  },
});

👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。  

示例代码

1.绘制 canvas 动画

<!-- index.tyml -->
<canvas
  type="2d"
  canvas-id="canvas"
></canvas>
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function () {
    render = new Render(this);
  },
 
  onReady() {
    render.renderCar();
  },
});
// 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.绘制 ImageData 和 Path2D

<!-- 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>
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (query) {
    render = new Render(this);
  },
 
  onReady() {
    render.renderImageData();
    render.renderPath2D();
  },
});
// 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);
  },
});

👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。  

3.绘制图表

<!-- tyml -->
<canvas canvas-id="f2" class="chart" />
<button bindtap="draw">渲染图表</button>
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * 生命周期函数--监听页面加载
   */
  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) },
    ]);
  },
});
// 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(); // 清除
      chart.interval().position('genre*sold').color('genre');
      // Step 2: 载入数据源
      chart.source(data);
      // Step 4: 渲染图表
      chart.render();
    } else {
      let canvas = await this.instance.getCanvasById('f2');
      // Step 1: 创建 Chart 对象
      chart = new F2.Chart({
        el: canvas,
        pixelRatio: this.instance.getSystemInfo().pixelRatio || 2, // 指定分辨率
      });
 
      // Step 2: 载入数据源
      chart.source(data);
 
      // Step 3:创建图形语法,绘制柱状图,由 genre 和 sold 两个属性决定图形位置,genre 映射至 x 轴,sold 映射至 y 轴
      chart.interval().position('genre*sold').color('genre');
 
      // Step 4: 渲染图表
      chart.render();
    }
  },
});

4.绘制 lottie 动画 & 调用逻辑层方法

<!-- tyml -->
<canvas type="2d" canvas-id="canvas5"></canvas>
// index.js
import Render from './index.rjs';
let render;
Page({
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (query) {
    render = new Render(this);
  },
  animationPlay: function (arg1, arg2) {
    console.log('绘制动画', arg1, arg2);
  },
  onReady() {
    render.renderLottie();
  },
});
// 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', // 只支持canvas
      loop: true,
      autoplay: true,
      animationData: lottieData,
      // path: animationPath,
      rendererSettings: {
        // 这里需要填 canvas
        canvas: canvas5,
        context: canvasContext,
        clearCanvas: true,
      },
    });
    this.instance.callMethod('animationPlay', 'arg1', 'arg2');
  },
});

示例仓库

1. 使用UChart绘制图表

Demo下载地址

2. 获取图片像素点

Demo下载地址

3.RJS插件系统

Demo下载地址

4.Canvas图形库

Demo下载地址

👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。  

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IoT砖家涂拉拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值