前言
作为一个前端cv工程师,之前没接触过动效开发,最近通过大帅老猿摸金校尉群内的一次全员实战项目,使用PIXI+GSAP写了一个刹车动效,不是很复杂但是成就感满满!
先看一下最终效果
分析
- 从图中可以看出整体分为4个部分:
- 可点击按钮
- 可旋转车闸
- 流动的线
- 静态自行车
- 同时我们需要思考几个问题:
- 按钮的水波纹是怎么实现的
- 怎么实现捏合车闸的效果
- 向斜方向运动的线怎么实现的
先看一下目录结构&资源
不到二百行js代码就可以实现这个效果,下面开始!
准备
相关文档
- PIXI:pixijs.com/
- GSAP:greensock.com/docs/v3/GSA…
解决图片跨域问题:由于PIXI加载图片的时候不能访问本地图片地址,如果用VScode开发,我们只需要安装插件 Live Preview启一个本地服务就可以解决。
需要引入的两个js库
动画容器
<body>
<div id="brakebanner"></div>
<script>
window.onload = function () {
let banner = new BrakeBanner("#brakebanner");
}
</script>
</body>
创建画布&挂载
// 创建项目
this.app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0xf3f3f3,
resizeTo: window, // ???
})
this.bike = null;
// 挂载
document.querySelector(selector).appendChild(this.app.view)
加载资源
this.loader = new PIXI.Loader()
this.stage = this.app.stage
// 加载图片资源
this.loader.add('btn.png', 'images/btn.png')
this.loader.add('btn_circle.png', 'images/btn_circle.png')
this.loader.add('brake_bike.png', 'images/brake_bike.png')
this.loader.add('brake_handlerbar.png', 'images/brake_handlerbar.png')
this.loader.add('brake_lever.png', 'images/brake_lever.png')
this.loader.load()
this.loader.onComplete.add(() => {
this.show()
})
绘制按钮
上面初步准备完成,但是看不到什么效果,接下来绘制按钮
let actionBtn = new PIXI.Container()
this.stage.addChild(actionBtn)
let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture)
let btnCircle = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture)
let btnCircle2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture)
actionBtn.addChild(btnImage)
actionBtn.addChild(btnCircle)
actionBtn.addChild(btnCircle2)
只需要上面几行代码,按钮就出来啦!但是按钮展示有问题,接下来进行调整
actionBtn.interactive = true; // 开启交互
actionBtn.buttonMode = true; // 鼠标移动上去变成小手
actionBtn.scale.x = actionBtn.scale.y = 0.5 // 调整按钮大小
btnImage.pivot.x = btnImage.pivot.y = btnImage.width / 2 // 更改旋转中心
btnCircle.pivot.x = btnCircle.pivot.y = btnCircle.width / 2
btnCircle2.pivot.x = btnCircle2.pivot.y = btnCircle2.width / 2
btnCircle.scale.x = btnCircle.scale.y = 0.8
actionBtn.x = actionBtn.y = 130
现在按钮展示已经完成了,但是怎么动起来呢?不知道大家有没有注意绘制按钮的时候创建了两个btnCircle(外面的黄色圈),此时只需要将这个圈添加放大效果并改变其透明度,重复这个效果就实现了水波纹动态:
gsap.to(btnCircle.scale, {duration: 1, x: 1.3, y: 1.3, repeat: -1})
gsap.to(btnCircle, {duration: 1, alpha: 0, repeat: -1})
按钮有了波纹动效,鼠标移动到按钮上时也变成了小手,我们的第一个动效完成,是不是很简单!
绘制自行车&车闸
这里我们需要注意将车闸的旋转中心改至车闸图片的右下角,并且需要计算它的位置以贴合到相应位置:
const bikeContainer = new PIXI.Container();
bikeContainer.scale.x = bikeContainer.scale.y = 0.25 // 缩小图片大小
// 加载车闸
const bikeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture)
bikeContainer.addChild(bikeLeverImage) // 注:代码1
// 更改车闸旋转中心和位置
bikeLeverImage.pivot.x = bikeLeverImage.pivot.y = 455
bikeLeverImage.x = 710
bikeLeverImage.y = 910
// 加载车架
const bikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture)
bikeContainer.addChild(bikeImage)
// 加载车把手
const bikeHandlerbarImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture)
bikeContainer.addChild(bikeHandlerbarImage)
this.stage.addChild(bikeContainer); // 注:代码2
return {bikeContainer, bikeLeverImage}
如果车把手和自行车连接(如上图蓝色框位置)处有一部分灰色。这是因为PIXI绘制图片的时候,先加载的在底层,因此只需要调整一下加载顺序就可以解决了(如上代码段中 注:代码1 和 注:代码2)
自行车完成了,但是车把手怎么能动起来呢?其实也很简单,上面已经设置了车闸的旋转中心为右下角,那么我们只需要按下按钮的时候,让车闸旋转一定角度,松开按钮时复原就可以了。这里又使用了GSAP加了动效:
actionBtn.on('mousedown', () => {
// bikeLeverImage.rotation = Math.PI / 180 * -30 // 逆时针旋转30度
gsap.to(bikeLeverImage, {duration: .6, rotation: Math.PI / 180 * -30})
})
actionBtn.on('mouseup', () => {
gsap.to(bikeLeverImage, {duration: .6, rotation: 0})
})
神不神奇~
再调整一下自行车位置到屏幕右下角
let resize = () => {
bikeContainer.x = window.innerWidth - bikeContainer.width + 10
bikeContainer.y = window.innerHeight - bikeContainer.height
}
resize()
window.addEventListener('resize', resize)
流动的线
现在就剩下相对复杂的部分--流动的线。 从效果中可以看出这些线最开始是一个个小圆点,下面直接用PIXI绘制:
// 1. 创建容器
let particleContainer = new PIXI.Container()
this.stage.addChild(particleContainer)
let particles = []
let colors = [0x333333, 0xf16604, 0xb5cea8, 0x777777]
for (let i = 0; i < 12; i++) {
// 2. 用pixi绘图去创建粒子图形
let gr = new PIXI.Graphics()
gr.beginFill(colors[Math.floor(Math.random() * colors.length)])
gr.drawCircle(0, 0, 6)
gr.endFill()
let pItem = {
sx: Math.random() * window.innerWidth,
sy: Math.random() * window.innerHeight,
gr: gr
}
particles.push(pItem)
gr.x = pItem.sx;
gr.y = pItem.sy;
particleContainer.addChild(gr)
}
点绘制完成了,我们结下来还有2个问题要解决:
- 点是怎么变成线的
- 线怎么向斜方向动起来
先说第一个问题,因为图像是由一个个像素点组成的,当这个圆拉伸的够细的时候,圆就变成了线,同时颗粒感就出来了。 我们知道不停改变x或y的坐标值就能实现水平或垂直方向的运动。如要朝其他方向运动的话,就需同时改变x、y并且还要解决角度问题,这样一来就有些麻烦了。但是我们可以换个思路,让圆点垂直运动,然后旋转画布,这样就简单很多! 但是怎么让这个线渲染到页面上呢?这里就要用到gsap.ticker,它就像 GSAP 引擎的心跳,使用的是requestAnimationFrame按帧对网页进行重绘。 下面上代码
// 改变容器的旋转中心
particleContainer.pivot.x = window.innerWidth / 2;
particleContainer.pivot.y = window.innerHeight / 2;
// 改变容器位置
particleContainer.x = window.innerWidth / 2
particleContainer.y = window.innerHeight / 2;
// 让容器旋转就实现了粒子斜着运动
particleContainer.rotation = Math.PI / 180 * 35
let speed = 0;
let status = true; // 骑行 false刹车
const loop = () => {
speed += 0.4
speed = Math.min(speed, 20)
// 持续移动
for (let i = 0; i < particles.length; i++) {
let pItem = particles[i];
pItem.gr.y += speed
// 纵向拉长 点变成线
if(speed >= 20) {
pItem.gr.scale.y = 20;
// x小则有颗粒感
pItem.gr.scale.x = .05;
}
// 超出边界后回到顶部继续移动
if(pItem.gr.y > window.innerHeight) pItem.gr.y = 0;
}
}
// 开启循环任务
gsap.ticker.add(loop)
此时我们已经完成90%的效果了,现在再给按钮添加事件:按下时让小圆点复位同时停止动画,抬起时再开启动画:
// pointerdown 兼容pc & 移动的点击事件
actionBtn.on('mousedown', () => {
particleContainer.pause()
gsap.to(bikeLeverImage, {duration: .6, rotation: Math.PI / 180 * -30})
})
actionBtn.on('mouseup', () => {
particleContainer.start()
gsap.to(bikeLeverImage, {duration: .6, rotation: 0})
})
function start() {
speed = 0
gsap.ticker.add(loop)
}
function pause() {
for (let i = 0; i < particles.length; i++) {
let pItem = particles[i];
pItem.gr.scale.y = 1;
pItem.gr.scale.x = 1;
// 停止时添加回弹效果 elastic.out
gsap.to(pItem.gr, {duration: .6, x: pItem.sx, y: pItem.sy, ease: 'elastic.out'})
}
gsap.ticker.remove(loop)
}
基本效果已经完成!但是怎么给运动的自动车添加抖动效果、按下按钮时让粒子缓慢停下呢?添加抖动很简单,更改自行车容器的旋转中心后,在loop中给自行车添加一定的旋转角度。其他的这里就不贴代码了,感兴趣的可以查看源码:github.com/haixia0107/…
PS:
在公众号里搜 大帅老猿,在他这里可以学到很多东西!快来和我一起学习吧!