此篇文章源于前段时间商业有个项目需求,要实现一个比较复杂的H5交互动画。那说到动画常用的技术方案无非是下面几种:
gif图
css3动画属性
原生Javascript实现
Canvas
WebGL
gif图和css3动画属性显然只能实现展示型动画,而通过原生代码实现交互动画又是很复杂的,同时还得考虑动画的兼容性和性能问题。WebGL因为可提供硬件加速渲染,其渲染性能肯定是高于canvas的,但考虑到canvas与WebGL兼容性对比(如下图所示)及综合我们的项目要求,最初方案选定了用canvas实现。
canvas兼容性 WebGL兼容性但交互动画的核心在于用户交互,用户交互都是基于事件的。在canvas上绘制的图形自身不支持DOM事件,只有canvas标签支持DOM事件监听,因此还需要对canvas事件进行封装,实现相对应事件的监听及处理,还挺复杂的,这时候想要是有个canvas框架就好了,网上去搜索并对比了常用的三款canvas框架fabric.js、pixi.js及Phaser.js如下:
框架 | canvas功能支持 | 渲染 | 量级 | 事件 | 端 |
---|---|---|---|---|---|
fabric.js | 全 | svg、canvas | 轻 | 封装了canvas标签监听事件 | pc |
pixi.js | 全 | webGL、canvas | 轻 | 绘制元素上直接支持事件 | pc、移动、桌面 |
phaser.js | 全 | webGL、canvas | 重 | 绘制元素上直接支持事件 | pc、移动、桌面 |
对比发现pixi在框架量级上及渲染上都特别优秀,而且它的api简单易用,事件操作也很简单在绘制的对象上可直接操作事件,同时提供有鼠标和移动端touch事件;而fabric在渲染、事件的灵活性及端的支持上不如pixi;phaser是基于pixi封装的,在pixi优秀的渲染基础上又封装了很多游戏功能如游戏键盘手柄输入、声音支持等,所以phaser更适合做游戏,它是一款游戏引擎。
综合上面的选型分析,PixiJs很适合我们用来做交互动画及小游戏。
那PixiJs具体是什么样的呢?
官网的介绍总结一下是这样的:它是一款基于canvas的2D WebGL渲染引擎,可以创建丰富的交互式图形、动画和游戏。pixi的目标是提供一个快速的、轻量级且是兼容所有设备的2D库,无需了解WebGL就可以让开发者享受到硬件加速,它默认使用WebGL渲染,但在浏览器不支持的情况下可优雅降级成Canvas渲染。同时对绘制内容支持完整的鼠标和触摸事件。
其特征总结起来就是:
支持WebGL渲染,因为能调用GPU渲染,所以渲染性能高
支持canvas渲染,当设备不支持WebGL时自动使用canvas渲染,也可以手动选择canvas渲染
简单易用的api,提供了很多封装的模块
丰富的交互事件,支持完整的鼠标和移动端touch事件
不侵入你的编码风格,可以自由选择使用它的多少功能
与其他插件或框架无缝集成
了解了pixi后看它是如何使用的,Pixi的引用很方便
npm i pixi.js
import * as PIXI from "pixi.js"
引入后我们去用pixi创建一个动画的流程大概是这样的:
创建舞台
创建画布
把画布挂载到DOM上
创建精灵
显示精灵
操作精灵
先解释下什么是舞台,什么是画布?
舞台是你的创作场景中所有可见对象的根容器,我们可以将它看做一个空盒子,我们在舞台上放的内容都会在画布上呈现,所有要渲染的对象是只有添加到舞台中才能被显示出来的。
画布就是一个渲染区域,把画布添加进DOM后,就会创建一个canvas标签,画布对象会默认选择WebGL引擎渲染模式。
pixi直接提供了一个应用类PIXI.Application可以自动创建舞台(stage)和画布(renderer),如下图所示:
(1)app.stage就是一个舞台对象,添加渲染对象到舞台上直接app.stage.addChild(XXX)就可以了。
(2)app.renderer是画布对象,如果你需要在创建canvas标签之后改变它的背景色,设置app.renderer对象的backgroundColor属性为一个任何的十六进制颜色即可
app.renderer.backgroundColor = 0xffffff;
如果你想获取画布的宽高,直接使用app.renderer.view.width 和app.renderer.view.height即可,同时还可以用画布的resize方法重新设置画布的宽高
app.renderer.autoResize = true;
app.renderer.resize(512, 512);
舞台和画布已经创建好,把Pixi应用对象创建出来的HTMLCanvasElement(即app.view)添加到dom上,就可以在浏览器上看到一个白色背景的canvas渲染区了,如下图所示:
document.getElementById('draw').appendChild(app.view)
现在我们已经有了舞台和画布,就可以往舞台上添加图片,然后在画布上显示了。
我们把能放到舞台上的特殊图像对象称为精灵,精灵就是我们能用代码控制的图片,你能够控制他们的位置、大小、层次,用它来产生交互和动画,是pixi制作动画的关键因子。
pixi有一个精灵类PIXI.Sprite,直接在创建精灵方法中传入单个图片、或者雪碧图、或者一个包含图像信息的JSON对象都可以创建一个或多个精灵。
但pixi因为用WebGL和GPU去渲染图像,所以图像需要转化成GPU可以处理的形式才行,我们把可以被GPU处理的图像称作纹理。一般不能直接在PIXI.Sprite里直接传图片,要传纹理才行。而怎么加载图像并将它转化成纹理呢?pixi提供了强大的loader对象可以通过loader把上面三种类型(单张、雪碧图、JSON)图像资源转化成纹理资源,如下面代码所示:
PIXI.loader
.add(['image1.png']) // 加载图片
.load(setup);
// 图片加载完成时用setup的方法来使用它
function setup() {
// 生成精灵
let sprite = new PIXI.Sprite(
// 图片转换成纹理
PIXI.loader.resources['image1.png'].texture
);
}
已经制作好了精灵,按照上面介绍的舞台对象操作方法把精灵添加到舞台上就可以在画布上显示图片了。
app.stage.addChild(sprite)
单个精灵已经创建好了,如果你的动画场景比较复杂想管理一组图片,那么可以用PIXI.Container对象,把一组精灵聚合起来,如果要同时操控这一组精灵,直接操作container对象就可以了,如下图所示就是创建了2行2列的小鸟精灵,这些精灵都添加到了容器container中且容器在画布居中旋转:
代码如下:
const container = new PIXI.Container();
app.stage.addChild(container);
const birdTexture = PIXI.Texture.fromImage('bird.png');
for (let i = 0; i < 4; i++) {
const bird = new PIXI.Sprite(birdTexture);
bird.width = 40
bird.height = 40
bird.anchor.set(0.5);
bird.x = (i % 2) * 40;
bird.y = Math.floor(i / 2) * 40;
container.addChild(bird);
}
// 设置container位置
container.x = 300;
container.y = 300;
// 设置container容器原点
container.pivot.x = container.width / 2;
container.pivot.y = container.height / 2;
// 循环旋转
app.ticker.add((delta) => {
container.rotation -= 0.01 * delta;
});
一个pixi实例实际上就是一个树状结构,由一个root container(app.stage)包含所有的renderable元素,而Container也可以包含其他Container,下图是pixi实例的树状结构:
单个精灵或者一组精灵创建完后,就可以控制精灵的位置、大小、层次等进行动画交互了,Pixi常用的交互事件有:
pointer事件(兼容鼠标和触摸屏的共同触发)
mouse事件(鼠标触发)
touch事件(触摸屏触发)
事件的运用在下面的例子中会有详细介绍。这五步大概涵盖了完整动画的创建流程,下面会用一个有天空背景,鼠标拖动小鸟在天空舞动的例子介绍下pixi动画的具体实现。
const app = new PIXI.Application({
width: 600,
height: 600,
antialias: true,
transparent: false,
resolution: 2,
autoResize: true,
backgroundColor: 0xffffff
})
const drawing: any = document.getElementById('draw')
drawing.appendChild(this.app.view)
PIXI.loader
.add('sky.png')
.load(setup)
function setup() {
let bgSprite = new PIXI.Sprite(PIXI.loader.resources['sky.png'].texture)
bgSprite.width = 600
bgSprite.height = 600
app.stage.addChild(bgSprite);
}
效果图如下:
let birdSprite = new PIXI.Sprite(PIXI.loader.resources['bird.png'].texture)
birdSprite.width = 80
birdSprite.height = 80
birdSprite.position.set(20, 20) // 设置位置
app.stage.addChild(birdSprite)
效果图如下:
// 给小鸟精灵添加事件
birdSprite
.on('pointerdown', this.onDragStart)
.on('pointermove', this.onDragMove)
.on('pointerup', this.onDragEnd)
.on('pointerupoutside', this.onDragEnd)
//开始拖拽
onDragStart(event) {
this.dragging = true
this.data = event.data
// 鼠标点击位置和小鸟位置的偏移量,用于移动计算
this.diff = { x: event.data.global.x - this.x, y: event.data.global.y - this.y }
}
//拖拽移动中
onDragMove() {
if (this.dragging) {
const newPosition = this.data.getLocalPosition(this.parent)
// 拖拽中保证小鸟不超过背景区域
this.x = Math.min(Math.max(this.boundary.left || 0, newPosition.x - this.diff.x), this.boundary.right)
this.y = Math.min(Math.max(this.boundary.top || 0, newPosition.y - this.diff.y), this.boundary.bottom)
}
}
//拖拽完成,松开鼠标或抬起手指
onDragEnd() {
if (this.dragging) {
this.dragging = false
this.data = null
}
}
至此怎么用pixi实现一个交互动画就讲完了,如果动画场景再设置复杂点,然后加上音频、加上动效等开发一个酷炫的小游戏也不在话下呢~,当然pixi不仅仅只有介绍的这些功能,它还支持绘制文本、绘制几何图形、支持20多种滤镜、设置遮罩层及提供动画属性等。下图是对pixi支持功能的一个细节分类:
图片来自网上pixi功能很强大,是好多人制作复杂动画和小游戏的首选,但学习pixi的中文资料不多,更复杂些的功能需要查看官方英文文档和研究源码了解,欢迎大家一起研究、探讨pixi的更多更好玩的功能!
Pixi官方文档
pixi_github
转发本文并留下评论,我们将抽取第 10 名留言者(依据公众号后台排序),转转纪念 T 恤一件,大家快转发起来吧~