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/ios string statusHeight 状态栏高度 number pixelRatio 像素比 number orientation 屏幕状态(横竖屏) string getBoundingClientRectById(id: string, instance)
获取对应节点相关信息,包含以下内容。
变量名 类型 left number right number top number bottom number width number height number 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
是实例方法名,arg1
,arg2
是入参数据。 - 逻辑层调用
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 动画 & 调用逻辑层方法
- 如需使用 lottie,请在
index.rjs
中引入lottie-miniapp
<!-- 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绘制图表
2. 获取图片像素点
3.RJS插件系统
4.Canvas图形库
👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。