1. 为什么选择PixiJS
PixiJS是一个非常快的2D sprite渲染引擎。作为一个Javascript的2D渲染器,PixiJS的目标是提供一个快速的、轻量级而且是兼任所有设备的2D库。它主要使用了WebGL技术,能帮助展示、驱动和管理富有交互性的图形、制作游戏,同时也提供无缝 Canvas 回退。使用JavaScript以及其他HTML5技术,结合PixiJS引擎,可以创建出丰富的交互式图形,跨平台的应用程序和游戏。
2. 实现目标
在网上找到了一些优秀的案例,比如:
Canvas%20mouse%20and%20touch%20events
在研究了一下这些案例的实现代码之后,觉得基于PixiJS实现一个类似的鼠标交互动画在代码层面应该会更简单一些,主要实现以下两点功能:
-
鼠标移动时,跟随鼠标路径生成一道粒子束;
-
鼠标点击时,模拟粒子群由中心向四周发射。
3. 实现过程
(1)创建应用和舞台
使用Pixi上的Application对象创建一个矩形显示区域, 它会自动生成一个HTML 元素,然后在canvas画布上显示图像。
javascript
const ratio = window.devicePixelRatio
const app = new Application({
view: this.$refs.starRef,
transparent: true,
antialias: true,
width: document.documentElement.clientWidth /ratio,
height: document.documentElement.scrollHeight / ratio,
resolution: ratio
})
此处值得注意的是,由于本次示例中是给一个滚动页面整体添加鼠标动画,因此所绘制的画布应当覆盖整个HTML文档,故而height的取值应基于scrollHeight属性。
stage(舞台)是Pixi的Container(容器)对象,该舞台对象将作为根容器来保存开发者希望Pixi显示的东西(精灵),可以通过如下方式访问它。
app.stage
(2) 创建精灵
由于本次动画中所生成的粒子都是一样的,因此使用Pixi的Sprite类,采用加载单个图像文件的形式来创建精灵。
ini
const star = Sprite.from('static/star.png')
if (isClick) {
star.x = mouse.x / window.devicePixelRatio
star.y = mouse.y / window.devicePixelRatio
star.vx = (Math.random() - 0.5) * 5
star.vy = (Math.random() - 0.5) * 5
} else {
star.x = (mouse.x + (Math.random() - 0.5) * radius) / window.devicePixelRatio
star.y = (mouse.y + (Math.random() - 0.5) * radius) / window.devicePixelRatio
star.vx = (Math.random() * 2) - 1
star.vy = (Math.random() * 2) - 1
}
const scale = 0.7 - Math.random() * 0.3
star.scale.set(scale, scale)
star.rotation = Math.random() * Math.PI
star.alpha = 0.5 + 0.5 * Math.random()
star.va = Math.random() * 0.1
stars.push(star)
app.stage.addChild(star)
}
通过x和y属性来定位精灵的位置,引入isClick参数来区分鼠标移动事件和鼠标点击事件,如果是鼠标移动事件,则生成的粒子的初始位置可围绕在鼠标附近一定范围内;如果是鼠标点击事件,则生成的多个粒子的初始位置均为鼠标当前位置。
通过scale.set方法对精灵进行随机缩放,美化视觉效果。
通过rotation属性来给精灵设置一个弧度值让它旋转。
通过alpha属性设置精灵的初始透明度。
创建完成之后将该精灵通过addChild方法加入到stage,并存入一个stars数组中便于精灵位移刷新显示和增删管理。
(3) 移动精灵
在上一步骤中还设置了vx和vy两个速度属性来控制精灵的移动速度,其中vx用于设置精灵在x轴上(水平)的速度和方向, vy用于在y轴上(垂直)设置精灵的速度和方向。 无需直接更改精灵的x和y值,只需通过这两个速度属性更新速度变量,即可实现精灵的移动。而且可以借鉴衍生出va这个消散属性,控制精灵的消失速度。
ini
function update (star) {
star.x += star.vx
star.y += star.vy
star.alpha -= star.va
}
(4)动画帧率更新
使用app.ticker方法实现动画的帧率更新,类似前端的requestAnimationFrame,当浏览器的显示频率刷新的时候,此函数会被执行。
ini
app.ticker.add(delta => gameLoop(delta))
function gameLoop (delta) {
for (let i = 0; i < stars.length; i++) {
update(stars[i])
if (stars[i].opacity <= 0 || stars[i].x < 0 || stars[i].x > app.renderer.width || stars[i].y < 0 || stars[i].y > app.renderer.height) {
app.stage.removeChild(stars[i])
stars.slice(i, 1)
}
}
}
每当浏览器的显示频率刷新, 则遍历存储在stars数组里的各个精灵,更新其位置,并判断其是否已经消失,判定条件满足以下其一即可:
- 透明度降为0
- 坐标不在画布内
若满足消失条件,则先通过removeChild方法在stage中删除该精灵,再在stars数组中删除记录,这一顺序切记不可颠倒。
(5)通过监听鼠标事件添加精灵
通过在window对象上监听mousemove和click事件来调用生成精灵的函数。应当注意的是,为了更好的动画效果,点击时一次性生成的粒子数目应远多于移动时。
csharp
window.addEventListener('mousemove', function (event) {
mouse.x = event.pageX
mouse.y = event.pageY
addSpriteMethods()
})
window.addEventListener('click', function (event) {
mouse.x = event.pageX
mouse.y = event.pageY
addSpriteMethods()
})
(6)适应窗口动态变化
监听浏览器窗口变化,实现以下两步操作:
-
重置外部容器的宽高;
-
使用renderer的resize方法更改画布大小,为了确保画布的大小调整到与分辨率匹配,autoResize需要设置为true。
javascript
window.onresize = () => {
const SIZE = {
w: document.documentElement.clientWidth,
h: document.documentElement.scrollHeight
}
this.$refs.starRef.style.width = SIZE.w + 'px'
this.$refs.starRef.style.height = SIZE.h + 'px'
app.renderer.resize(SIZE.w / window.devicePixelRatio, SIZE.h / window.devicePixelRatio)
}
(7)高分屏下的兼容性问题
在高分屏下,通常会出现图形模糊,因此和canvas解决模糊的思路一样,都需要现将画布进行放大到和实际像素一致,但这一过程中容易造成坐标计算错位的情况,需要格外注意两个要点:
- 创建应用时设置resolution属性为设备像素比(window.devicePixelRatio);
- 创建的画布的上下文宽高应是css宽高的1 /window.devicePixelRatio。
(8)提高精灵的实时亮度
通过滤镜来实现,可以使用PIXI ColorMatrixFilter执行此操作,设置matrix属性,通过矩阵运算提高纹理亮度。但此方法调参难度较大,慎用。
ini
var colorMatrix = [
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1
]
var filter = new PIXI.ColorMatrixFilter()
filter.matrix = colorMatrix
stage.filters = [filter]
也可以直接通过使用brightness方法来调节滤镜亮度:
scss
filter.brightness(num) //num∈[0-1], where 0 is black
4. 小结
PixiJS的API灵活友好、功能丰富,相比于前端其他的动画制作方法,有利于简化代码,而且具有快速、高效的特点,特别适用于做包含大量元素的粒子动画。本次示例只是一次较为基础的简单实践,可在此思路基础上可以延续拓展出更多动画细节,欢迎多多交流。