简简单单实现一个刹车效果动画 | 猿创营

571 阅读4分钟

前言

作为一枚菜鸟, 一直能喜欢做一些酷炫的动画特效, 但是一直没有机会接触到。直到前段时间,在猿创营中,跟着大帅的直播学习了一把刹车的动画效果后,有了这边文章记录一下自己的学习内容。

参考文档

我们看一下动画效果:

刹车动效.gif

从上图的效果上, 我们可以看到要实现的效果:按钮有波纹状的效果和粒子快速滑动的效果,点击按钮有刹车动画,同时,粒子停住。放开按钮后,粒子恢复滑动。

在开始编码前,我们整理一下核心思路:

  1. 用PIXI创建根实例用来存放动画并挂载到dom上
  2. 创建主容器并加载到根实例上
  3. 通过loader加载图片资源
  4. 资源加载后并显示到页面上
  5. 开始使用gsap添加动画效果到按钮上并实现刹车效果
  6. 添加粒子并把它加载到页面上
  7. 让粒子快速滑动起来
  8. 点击按钮停止粒子动画

整理出上面的思路后,我们开始编码实现:

首先,我们先创建一个BrakeBanner类。在初始化的时候,创建一个根实例并将它挂载到页面上,这个根实例将用来存放各种需要添加的资源。具体代码如下:

class BrakeBanner{
  constructor(selector){
    // 创建实例
    this.app = new PIXI.Application({
      width: window.innerWidth,
      height: window.innerHeight,
      backgroundColor: 0xffffff,
      resizeTo: window
    })
    // 挂载到dom上
    document.querySelector(selector).appendChild(this.app.view)
    this.stage = new PIXI.Container() // 容器
    this.app.stage.addChild(this.stage) // 将新增容器添加到根容器上
  }
}

接着,我们通过loader将图片资源引并显示到页面上,这里看一下具体如下:

addLoader() {
    // 资源加载器
    const loader = new PIXI.Loader()
    // 资源入队
    images.forEach(img => {
      loader.add(img, `images/${img}`)
    })
    // 资源加载
    loader.load()
    // 加载完成后回调
    loader.onComplete.add(() => {
      this.show()
    })
    return loader
  }

上面我们创建了一个addLoader方法,是用来加载所有的图片,然后我们在constructor中调用该方法:

constructor(selector){
  //...省略部分代码...
  this.loader = this.addLoader()
}

这样我们就把资源加载进来了。只是加载进来还是不够的,我们现在要把图片显示到页面上,所以这里我们增加一个方法show用来加图片加到页面上:

show() {
    // 加载按钮图片并设置中心点
    const imgContainer = this.createActionButton()
    imgContainer.x = imgContainer.y = 400
    
    const bikeContainer = new PIXI.Container()
    this.stage.addChild(bikeContainer)
    bikeContainer.scale.x = bikeContainer.scale.y = 0.3

    const leverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture)
    bikeContainer.addChild(leverImage)
    leverImage.pivot.x = leverImage.pivot.y = 455
    leverImage.x = 722
    leverImage.y = 900

    const bikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture)
    bikeContainer.addChild(bikeImage)

    const handlerImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture)
    bikeContainer.addChild(handlerImage)

    this.stage.addChild(imgContainer)
}

我们在show中调用了createActionButton方法,同时,通过PIXI.Container创建用来存放车体图片的容器。而12行则设置了刹车的中心点,13和14行则调整了它的坐标位置。那调用createActionButton方法是做了什么?我们先看一下代码:

createActionButton() {
    // 加载按钮容器
    const imgContainer = new PIXI.Container()
    // 将按钮图片纹理加入到容器中
    let btnImg = new PIXI.Sprite(this.loader.resources['btn.png'].texture)
    imgContainer.addChild(btnImg)

    let btnCircle = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture)
    imgContainer.addChild(btnCircle)

    let btnCircle2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture)
    imgContainer.addChild(btnCircle2)

    // 设置中心点
    btnImg.pivot.x = btnImg.pivot.y = btnImg.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
    // 引入gsap动画
    gsap.to(btnCircle.scale, {
      duration: 1,
      x: 1.3,
      y: 1.3,
      repeat: -1
    })
    gsap.to(btnCircle, {
      duration: 1,
      alpha: 0,
      repeat: -1
    })
    return imgContainer
  }

createActionButton方法实际上是做了两件事情:将按钮的资源加载到容器上和给按钮添加波纹的效果。

3到17行的代码,都是拿到按钮的图片资源并添加到imgContainer的容器中,然后调整图片的中心点和坐标

20行之后的代码,则是通过gsap来实现波纹状的效果。

效果图如下:

image.png **现在,**刹车和按钮都加载了,我们要实现刹车的动画了。在show方法中,我们添加一下鼠标事件,代码如下:

show() {
    // ......
    imgContainer.interactive = true
    imgContainer.buttonMode = true

    imgContainer.on('mousedown', () => {
      gsap.to(leverImage, {
        duration: .6,
        rotation: Math.PI/180*-30,
      })
    })
    imgContainer.on('mouseup', () => {
      gsap.to(leverImage, {
        duration: .6,
        rotation: 0,
      })
    })
}

先给按钮(imgContainer)设置两个属性interactive 和buttonMode ,这样才能有交互效果,给它添加两个事件后,刹车动画就完成了,短短的几行代码就实现了。

效果图如下:

只有刹车.gif

最后,就是把粒子的效果实现了。

还是在show方法,我们先把粒子创建出来:

show() {
    //.....
    // 创建粒子
    let particleZoneSize = window.innerWidth
    let particleContainer = new PIXI.Container()
    this.stage.addChild(particleContainer)

    let particles = []
    const colors = [0xf1cf54, 0xb5cea8, 0xf1cf54, 0x818181, 0x000000];
    for (let i = 0; i < 10; i++) {
      let gr = new PIXI.Graphics()
      gr.beginFill(colors[Math.floor(Math.random() * colors.length)])
      gr.drawCircle(0,0,6)
      gr.endFill()
      let parItem = {
        sx: Math.random() * particleZoneSize,
        sy: Math.random() * window.innerHeight,
        gr
      }
      gr.x = parItem.sx
      gr.y = parItem.sy
      particleContainer.addChild(gr)
      particles.push(parItem)
    }

    let zWidth = particleZoneSize/2
    let zHeight = window.innerHeight / 2
    particleContainer.pivot.set(zWidth,zWidth)
    particleContainer.rotation = (35 * Math.PI) / 180
    particleContainer.x = zWidth
    particleContainer.y = zHeight
}

这里还是一样先创建一个容器particleContainer ,然后用PIXI.Graphics创建形状,beginFill填充颜色,调用drawCircle绘制粒子,最后用数组将粒子数据存放起来。

**现在,**我们已经创建粒子了,接下来就让他们动起来,代码如下:

show() {
    // ....
    let speed = 0
    function loop() {
      speed += .5
      speed = Math.min(speed, 20)
      for (let i = 0; i < particles.length; i++) {
        let pItem = particles[i]
        let gr = pItem.gr
        gr.y += speed

        if (speed >= 20) {
          gr.scale.y = 40
          gr.scale.x = .03
        }
        if (gr.y > window.innerHeight) {
          gr.y = 0
        }
      }
    }

    function start() {
      speed = 0
      gsap.ticker.add(loop)
    }

    function pause() {
      gsap.ticker.remove(loop)
      for (let i = 0; i < particles.length; i++) {
        let pItem = particles[i]
        let gr = pItem.gr

        gr.scale.y = 1
        gr.scale.x = 1
        gsap.to(gr, {
          duration: .6,
          x: pItem.sx,
          y: pItem.sy,
          ease: 'elastic.out'
        })
      }
    }

    start()
}

我们添加了start,pause和loop方法。最后调用了start方法,让粒子动起来。

其中,24行的代码里我们将loop添加到gsap的ticker中。

28行的代码里我们将loop移出ticker中。

同时将start和pause分别放到mouseup和mousedown事件中,具体如下:

// ...
imgContainer.on('mousedown', () => {
      gsap.to(leverImage, {
        duration: .6,
        rotation: Math.PI/180*-30,
      })
     ** pause()**
})
imgContainer.on('mouseup', () => {
      gsap.to(leverImage, {
        duration: .6,
        rotation: 0,
      })
      **start()**
})
// ...

这样我们就实现了点击粒子停止,放开粒子快速滑动的效果。最终的效果就如同我们一进来的动态图一样。

至此,我们就可以用PIXI和gsap来愉快的实现我们的各种酷炫效果了。各种小伙伴快快用起来~~~

打开广告

公众号里搜 大帅老猿,他做技术外包很靠谱