PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

961 阅读4分钟

前言

作为一个前端cv工程师,之前没接触过动效开发,最近通过大帅老猿摸金校尉群内的一次全员实战项目,使用PIXI+GSAP写了一个刹车动效,不是很复杂但是成就感满满!

点击查看原版vanmoof商城

先看一下最终效果

mnggiflab-download (3).gif

分析

  • 从图中可以看出整体分为4个部分:
  1. 可点击按钮
  2. 可旋转车闸
  3. 流动的线
  4. 静态自行车
  • 同时我们需要思考几个问题:
  1. 按钮的水波纹是怎么实现的
  2. 怎么实现捏合车闸的效果
  3. 向斜方向运动的线怎么实现的

先看一下目录结构&资源 image.png 不到二百行js代码就可以实现这个效果,下面开始!

准备

相关文档

解决图片跨域问题:由于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)
image.png

只需要上面几行代码,按钮就出来啦!但是按钮展示有问题,接下来进行调整

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
image.png

现在按钮展示已经完成了,但是怎么动起来呢?不知道大家有没有注意绘制按钮的时候创建了两个btnCircle(外面的黄色圈),此时只需要将这个圈添加放大效果并改变其透明度,重复这个效果就实现了水波纹动态:

gsap.to(btnCircle.scale, {duration: 1, x: 1.3, y: 1.3, repeat: -1})
gsap.to(btnCircle, {duration: 1, alpha: 0, repeat: -1})
ScreenFlow4.gif

按钮有了波纹动效,鼠标移动到按钮上时也变成了小手,我们的第一个动效完成,是不是很简单!

绘制自行车&车闸

这里我们需要注意将车闸的旋转中心改至车闸图片的右下角,并且需要计算它的位置以贴合到相应位置:

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}

image.png

如果车把手和自行车连接(如上图蓝色框位置)处有一部分灰色。这是因为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})
})

ScreenFlow5.gif 神不神奇~

再调整一下自行车位置到屏幕右下角

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)
}

image.png

点绘制完成了,我们结下来还有2个问题要解决:

  1. 点是怎么变成线的
  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)

mnggiflab-download (1).gif 此时我们已经完成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)
}

mnggiflab-download (2).gif 基本效果已经完成!但是怎么给运动的自动车添加抖动效果、按下按钮时让粒子缓慢停下呢?添加抖动很简单,更改自行车容器的旋转中心后,在loop中给自行车添加一定的旋转角度。其他的这里就不贴代码了,感兴趣的可以查看源码:github.com/haixia0107/…

PS: 在公众号里搜 大帅老猿,在他这里可以学到很多东西!快来和我一起学习吧!