简体中文 | English
Canvas 渲染引擎,支持SVG,兼容了小程序、小游戏和 Web
- Wechart by Cax

- Web DEMO & → Source Code
- Learn Once, Write Anywhere(小程序、小游戏、PC Web、Mobile Web)
- Write Once, Run Anywhere(小程序、小游戏、Web只需要修改
new Stage传入参数即可) - 支持小程序、小游戏以及 Web 浏览器渲染
- 小程序、小游戏和 Web 拥有相同简洁轻巧的 API
- 高性能且松耦合的渲染架构
- 超轻量级的代码体积
- 支持 Canvas 元素管理
- 支持 Canvas 元素事件体系
- 图灵完备的 group 嵌套体系
- 内置 to2to 的跨平台运动引擎
- 支持可以变形的 clip 裁剪体系
- 内置文本、位图、序列帧、绘图对象和多种矢量绘制对象
- 支持 SVG Path 渲染
- 支持几乎全部的 CSS 滤镜和其他常用滤镜
- 内置图片加载器
- 一分钟入门小程序 cax 使用
- 一分钟入门小游戏 cax 使用
- 一分钟入门 Web cax 使用
- 内置对象
- 属性
- 方法
- 事件
- 运动
- 裁剪
- 自定义对象
- 图片加载器
- 谁在使用?
- 微信交流群
- License
到 GitHub 下载 cax 自定义组件,然后小程序引入 cax 自定义组件:
└── cax
├── cax.js
├── cax.json
├── cax.wxml
├── cax.wxss
└── index.js
在 page 或者 component 里声明依赖:
{
"usingComponents": {
"cax":"../cax/cax"
}
}在的 wxml 里引入 cax 标签:
<cax id="myCanvas"></cax>在 js 里渲染逻辑:
import cax from '../cax/index'
Page({
onLoad: function () {
//比 web 里使用 cax 多传递 this,this 代表 Page 或 Component 的实例
const stage = new cax.Stage(200, 200, 'myCanvas', this)
const rect = new cax.Rect(100, 100, {
fillStyle: 'black'
})
rect.originX = 50
rect.originY = 50
rect.x = 100
rect.y = 100
rect.rotation = 30
rect.on('tap', () => {
console.log('tap')
})
stage.add(rect)
stage.update()
}
})效果如下所示:
除了 tap 事件,也可以帮 rect 绑定其他触摸事件:
rect.on('touchstart', () => {
console.log('touchstart')
})
rect.on('touchmove', () => {
console.log('touchmove')
})
rect.on('touchend', () => {
console.log('touchend')
})主要,小程序事件触发是根据元素的矩形区域,所以需要设置 width 和 height 才能绑定触发事件。
到 GitHub 下载 cax 小游戏示例,目录结构和运行效果如下:
const stage = new cax.Stage()和小程序以及 Web 不同的是,小游戏创建 Stage 不需要传任何参数。
通过 npm 或者 CDN 获取:
npm i cax使用方法:
import cax from 'cax'
const stage = new cax.Stage(200, 200, 'body')
cax.loadImgs({
imgs: ['./test.jpg'],
complete: (imgs) => {
const img = imgs[0]
const bitmap = new cax.Bitmap(img)
bitmap.x = stage.width / 2
bitmap.y = stage.height / 2
bitmap.rotation = -10
bitmap.originX = img.width / 2
bitmap.originY = img.height / 2
bitmap.filter('blur(10px)')
stage.add(bitmap)
stage.update()
}
})除了 Stage 构造函数比小程序第四个参数 this,其他使用方式都一样。
用于分组, group 也可以嵌套 group,父容器的属性会叠加在子属性上, 比如:
- group 的 x 是 100, group 里的 bitmap 的 x 是 200, 最后 bitmap 渲染到 stage 上的 x 是 300
- group 的 alpha 是 0.7, group 里的 bitmap 的 alpha 是 0.6, 最后 bitmap 渲染到 stage 上的 alpha 是 0.42
const group = new cax.Group()
const rect = new cax.Rect(100, 100, {
fillStyle: 'black'
})
group.add(rect)
stage.add(group)
stage.update()group 拥有常用的 add 和 remove 方法进行元素的增加和删除。先 add 的会先绘制,所有后 add 的会盖在先 add 的上面。
添加对象
groupObj.add (child) 移除对象
groupObj.remove (child)清空子对象
groupObj.empty ()使用一个对象替代子对象
groupObj.replace(current, prev)最大的顶层容器,继承自 Group,所以 Group 拥有的方法它全都有。
任何元素添加到舞台上是看不到的,你必须执行 update 方法。
stage.update()任何元素属性的修改请执行 stage.update() 来更新舞台,或者放在定时器里:
cax.tick(stage.update.bind(stage))当 canvas 的宽高 和 canvas 的像素不对应的时候,事件触发位置是不准确的,你可以使用 scaleEventPoint 方法使事件校正准确。
//canvas 宽高是像素的一半
stage.scaleEventPoint(0.5, 0.5)是否禁用鼠标或触摸移动的事件检测。
stage.disableMoveDetection = true这里需要注意,Web 的 disableMoveDetection 默认是 false,微信小游戏默认是 true。所以微信小游戏想要监听 touchmove 和 drag 事件的话需要在创建完 Stage 之后执行下面代码开启监听:
stage.disableMoveDetection = false设置 touchmove 和 mousemove 检测的间隔
//每秒检测两次
stage.moveDetectionInterval = 500const bitmap = new cax.Bitmap(img)
stage.add(bitmap)
stage.update()如果只传 url 而不是 Image 对象的实例,需要这样:
const bitmap = new cax.Bitmap('./wepay.png', ()=>{
stage.update()
})
stage.add(bitmap)这里需要注意小程序需要配置 downloadFile 需要配置合法域名才能正常加载到图片。
可以设置图片裁剪显示区域,和其他 transform 属性:
bitmap.rect = [0, 0, 170, 140]
bitmap.x = 200序列帧动画组件,可以把任意图片的任意区域组合成一串动画。
const sprite = new cax.Sprite({
framerate: 7,
imgs: ['./mario-sheet.png'],
frames: [
// x, y, width, height, originX, originY ,imageIndex
[0, 0, 32, 32],
[32 * 1, 0, 32, 32],
[32 * 2, 0, 32, 32],
[32 * 3, 0, 32, 32],
[32 * 4, 0, 32, 32],
[32 * 5, 0, 32, 32],
[32 * 6, 0, 32, 32],
[32 * 7, 0, 32, 32],
[32 * 8, 0, 32, 32],
[32 * 9, 0, 32, 32],
[32 * 10, 0, 32, 32],
[32 * 11, 0, 32, 32],
[32 * 12, 0, 32, 32],
[32 * 13, 0, 32, 32],
[32 * 14, 0, 32, 32]
],
animations: {
walk: {
frames: [0, 1]
},
happy: {
frames: [5, 6, 7, 8, 9]
},
win: {
frames: [12]
}
},
playOnce: false,
currentAnimation: "walk",
animationEnd: function () {
}
});跳到当前 animationName 并开始播放
spriteObj.gotoAndPlay(animationName)跳到当前 animationName 并开始停止
spriteObj.gotoAndStop(animationName)跳到当前 animationName 并开始播放,播完一轮销毁自己。常用于爆炸
spriteObj.gotoAndPlayOnce(animationName)文本对象
const text = new cax.Text('Hello World', {
font: '20px Arial',
color: '#ff7700',
baseline: 'top'
})获取文本宽度
textObj.getWidth()绘图对象,用于使用基本的连缀方式的 Canvas 指令绘制图形。
const graphics = new cax.Graphics()
graphics
.beginPath()
.arc(0, 0, 10, 0, Math.PI * 2)
.closePath()
.fillStyle('#f4862c')
.fill()
.strokeStyle('black')
.stroke()
graphics.x = 100
graphics.y = 200
stage.add(graphics)特别注意,如果你在某个循环中执行 graphics 连缀绘制操作,请务必加上 clear() 方法,不然路径叠加到你的浏览器不堪重负:
cax.setInterval(function(){
graphics
.clear()
.beginPath()
.arc(0, 0, 10, 0, Math.PI * 2)
.stroke()
}, 16)与 Graphics 不同的是, Shape 一般拥有有限的宽高,所以可以使用离屏 Canvas 进行缓存。下面这些属于 Shape。
const rect = new cax.Rect(200, 100, {
fillStyle: 'black'
})const circle = new cax.Circle(10)const ellipse = new cax.Ellipse(120, 20)注意:从技术上小游戏和 Web 可以离屏 Canvas,小程序不行,因为小程序不支持动态创建离屏 Canvas。
Element 是多种元素的组合,如 Bitmap、Group、 Text、 Shape 等混合起来的图像。
const button = new cax.Button({
width: 100,
height: 40,
text: "Click Me!"
})| 属性名 | 描述 |
|---|---|
| x | 水平偏移 |
| y | 竖直偏移 |
| scaleX | 水平缩放 |
| scaleY | 竖直缩放 |
| scale | 同时设置或读取 scaleX 和 scaleY |
| rotation | 旋转 |
| skewX | 歪斜 X |
| skewY | 歪斜 Y |
| originX | 旋转基点 X |
| originY | 旋转基点 Y |
| 属性名 | 描述 |
|---|---|
| alpha | 元素的透明度 |
注意这里父子都设置了 alpha 会进行乘法叠加。
| 属性名 | 描述 |
|---|---|
| compositeOperation | 源图像绘制到目标图像上的叠加模式 |
注意这里如果自身没有定义 compositeOperation 会进行向上查找,找到最近的定义了 compositeOperation 的父容器作为自己的 compositeOperation。
| 属性名 | 描述 |
|---|---|
| cursor | 鼠标移上去的形状 |
| 属性名 | 描述 |
|---|---|
| fixed | 是否固定定位,默认是 false 设置成 true 不会叠加祖辈们的 transform 属性 |
| 属性名 | 描述 |
|---|---|
| shadow | 阴影 |
使用方式:
obj.shadow = {
color: '#42B035',
offsetX: -5,
offsetY: 5,
blur: 10
}| Name | Describe |
|---|---|
| stage | 或者自己所在的 stage |
使用方式:
obj.stage销毁自己
obj.destroy()| 事件名 | 描述 |
|---|---|
| tap | 手指触摸后马上离开 |
| touchstart | 手指触摸动作开始 |
| touchmove | 手指触摸后移动 |
| touchend | 手指触摸动作结束 |
| drag | 拖拽 |
| 事件名 | 描述 |
|---|---|
| click | 元素上发生点击时触发 |
| mousedown | 当元素上按下鼠标按钮时触发 |
| mousemove | 当鼠标指针移动到元素上时触发 |
| mouseup | 当在元素上释放鼠标按钮时触发 |
| mouseover | 当鼠标指针移动到元素上时触发 |
| mouseout | 当鼠标指针移出元素时触发 |
| tap | 手指触摸后马上离开 |
| touchstart | 手指触摸动作开始 |
| touchmove | 手指触摸后移动 |
| touchend | 手指触摸动作结束 |
| drag | 拖拽 |
cax 内置了 to 的能力以连缀的方式写运动效果:
const easing = cax.To.easing.elasticInOut
cax.To.get(bitmap)
.to({ y: 340, rotation: 240 }, 2000, easing)
.begin(() => {
console.log("Task one has began!")
})
.progress(() => {
console.log("Task one is progressing!")
})
.end(() => {
console.log("Task one has completed!")
})
.wait(500)
.to()
.rotation(0, 1400, easing)
.begin(() => {
console.log("Task two has began!")
})
.progress(() => {
console.log("Task two is progressing!")
})
.end(() => {
console.log("Task two has completed!")
})
.start();to和to之间的是并行to和wait之前的是串行to和to之间的 与 下一个to和to之间的是串行
有点绕,但是很直观,慢慢体会。
当然,也可以通过 set 方法支持任意属性的运动,如:
.set('y', 240, 2000, cax.easing.elasticInOut)等同于
.y(240, 2000, cax.easing.elasticInOut)如果想要循环播放的话可以使用 cycle 方法:
cax.To.get(bitmap)
.to()
.y(340, 2000, cax.easing.elasticInOut)
.to
.y(0, 2000, cax.easing.elasticInOut)
.cycle()
.start()这里需要注意,和 tween.js 不同,cax 把 easing 命名全改成了一个单词的驼峰命名。如 Cubic.In 变成了 cubicIn。
const stage = new cax.Stage(600, 400, 'body')
const bitmap = new cax.Bitmap('./wepay-diy.jpg', () => {
stage.update()
})
const clipPath = new cax.Graphics()
clipPath.arc(40, 40, 25, 0, Math.PI * 2)
bitmap.clip(clipPath)
stage.add(bitmap)使用下面的代码可以得到同样的效果:
const clipPath = new cax.Graphics()
clipPath.x = 40
clipPath.y = 40
clipPath.arc(0, 0, 25, 0, Math.PI * 2)所以,裁剪区域也是支持所有 transform 属性(x,y,scaleX,scaleY,rotation,skewX,skewY,originX,originY)。
自定义 Shape 继承自 cax.Shape:
class Sector extends cax.Shape {
constructor (r, from, to, option) {
super()
this.option = option || {}
this.r = r
this.from = from
this.to = to
}
draw () {
this.beginPath()
.moveTo(0, 0)
.arc(0, 0, this.r, this.from, this.to)
.closePath()
.fillStyle(this.option.fillStyle)
.fill()
.strokeStyle(this.option.strokeStyle)
.lineWidth(this.option.lineWidth)
.stroke()
}
}使用 Shape:
const sector = new Sector(10, 0, Math.PI/6, {
fillStyle: 'red'
lineWidth: 2
})
stage.add(sector)
stage.update()自定义 Element 继承自 cax.Group:
class Button extends cax.Group {
constructor (option) {
super()
this.width = option.width
this.roundedRect = new cax.RoundedRect(option.width, option.height, option.r)
this.text = new cax.Text(option.text, {
font: option.font,
color: option.color
})
this.text.x = option.width / 2 - this.text.getWidth() / 2 * this.text.scaleX
this.text.y = option.height / 2 - 10 + 5 * this.text.scaleY
this.add(this.roundedRect, this.text)
}
}
export default Button使用:
const button = new cax.Button({
width: 100,
height: 40,
text: "Click Me!"
})一般情况下,稍微复杂组合体都建议使用继承自 Group,这样利于扩展也方便管理自身内部的元件。 小游戏的 DEMO 里的 Player、Bullet、Enemy、Background 全都是继承自 Group。 Wechart 的所有图表全都是继承自 Group。
cax.loadImg({
img: './a.png',
complete: function(img){
}
})加载多张图片:
cax.loadImgs({
img: ['./a.png','./b.png'],
progress: function(progress){
console.log(progress) //0.5 and then 1
},
complete: function(imgs){
console.log(imgs[0]) //Image(a.png)
console.log(imgs[1]) //Image(b.png)
}
})MIT





