一周赚了好几个W,手把手教你用PIXI, GSAP 制作刹车视觉动效

1,207 阅读3分钟

制作刹车动效分几步

hourse.png

制作刹车动效总共分3步。

  • 第一步:是使用Pixi上的Application对象创建一个矩形显示区域。 它会自动生成一个HTML <canvas>元素
  • 第二步:您需要创建一个称为stage的特殊Pixi容器对象在canvas画布上显示图像。
  • 第三步:创建 Sprite 显示在 stage 上, 这样你就可以看到完美的视图了。

flow.png

创建一个应用

首先引入 pixi, gasp

<script src="https://pixijs.download/release/pixi.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>

创建一个 app 并添加到容器 dom 中

<div id = 'container'></div>
<script>
    const container = document.querySelector('#container');
    const app = new PIXI.Application({
      width: window.innerWidth,
      height: window.innerHeight,
      backgroundColor: 0xff0000,
    })
    container.appendChild(app.view);
</script>

这个时候通过审查元素,你应该可以看到一个 canvas 已经插入到 dom 中。

创建一个 Sprite 并显示在 stage 中

loader 方法的详细使用

const loader = PIXI.loader;
const stage = app.stage;

loader
  .add("btn.png", "images/btn.png") // add(name, url, optionObject, callbackFunction) 
  .add("btn_circle.png", "images/btn_circle.png") // 一个圆圈
  .load(setup);
  
  function setup() {
      const btnSprite = new PIXI.Sprite(PIXI.loader.resources['btn.png'].texture);
       const btnCircleSprite = new  PIXI.Sprite(PIXI.loader.resources['btn_circle.png'].texture);
      app.stage.addChild(btnSprite)
      app.stage.addChild(btnCircleSprite)
  }

并调整一个默认位置, 按照视图左右居中, 为了方便布局, 把这两个 Sprite 作为一个整体


const actionContainer = new PIXI.Container();

actionContainer.x = actionContainer.y = window.innerWidth / 2; //  让容器左右居中
// 让按钮和圆圈在容器中居中
btnSprite.pivot.x = btnSprite.pivot.y = btnSprite.width / 2;   
btnCircleSprite.pivot.x = btnCircleSprite.pivot.y = btnCircleSprite.width / 2;
btnCircleSprite2.pivot.x = btnCircleSprite2.pivot.y = btnCircleSprite2.width / 2;
function setup() {
  const btnSprite = new PIXI.Sprite(PIXI.loader.resources['btn.png'].texture);
  const btnCircleSprite = new  PIXI.Sprite(PIXI.loader.resources['btn_circle.png'].texture);
  actionContainer.addChild(btnSprite)
  actionContainer.addChild(btnCircleSprite)
  app.stage.addChild(actionContainer)
}

再加入一些涟漪波纹的效果, 这里使用 gsap 做动画效果

   btnCircleSprite.scale.x = btnCircleSprite.scale.y = 0.7;  // 设置初始缩放为 0.7
    gsap.to(btnCircleSprite.scale, {  // 经过1s 从 0.7 变成 1.3 并且无限循环
      duration: 1,
      x: 1.3,
      y: 1.3,
      repeat: -1,
    });
    gsap.to(btnCircleSprite, { // 另外加了一个透明度的渐变
      duration: 1,
      alpha: 0,
      repeat: -1,
    });

最终的效果是这样的

btn.gif

创建更多的 Sprite 到 stage 中

使用Pixi的 Sprite 类来创建精灵。创建方法有三种:

  • 通过单个图像文件
  • 通过雪碧图
  • 通过纹理贴图

我这里都是通过单个图像文件创建的。

loader.add("brake_bike.png", "images/brake_bike.png")
    .add("brake_handlerbar.png", "images/brake_handlerbar.png")
    .add("brake_lever.png", "images/brake_lever.png")
    .load(setup);

    
// 同理创建一个 bikeContainer
const bikeContainer = new PIXI.Container();
    
function setup() {
  const brakeBikeSprite = new PIXI.Sprite(PIXI.loader.resources['brake_bike.png'].texture);
  const brakeHandlerbarSprite = new PIXI.Sprite(PIXI.loader.resources['brake_handlerbar.png'].texture);
  const brakeLeverSprite = new PIXI.Sprite(PIXI.loader.resources['brake_lever.png'].texture);
  
  bikeContainer.addChild(brakeBikeSprite)
  bikeContainer.addChild(brakeHandlerbarSprite)
  bikeContainer.addChild(brakeLeverSprite)
  app.stage.addChild(bikeContainer)
}

所有的资源都加载进来了, 并且车轮和把手都是固定的, 只有手刹是可以的活动的,我想大家都骑过自行车,手刹的活动就是围绕一点的旋转

bike.png

添加手刹的动效

  • 让按钮可以点击
 actionContainer.interactive = true; // 激活按钮点击功能
 actionContainer.buttonMode = true; // 鼠标放在按钮上是 cursor: pointer; 的效果
  • 点击按钮手刹旋转 -30°,松开按钮手刹恢复原状 0°
actionContainer.on("mousedown", () => {
  gsap.to(brakeLeverSprite, {
    duration: 0.6,
    rotation: (Math.PI / 180) * -30,
  });
});
actionContainer.on("mouseup", () => {
  gsap.to(brakeLeverSprite, {
    duration: 0.6,
    rotation: 0,
  });
});

添加一些速度斜线, 给人高速行驶的感觉

  • 随机生成 15 个小点, 然后小点按照一定的加速度向下移动,把这些点看成一个整体放到一个容器内。
  • 旋转容器到一定的角度
  • 小点的 y 轴大小大于容器后,再重置 y 值为 0, 一直这样无限循环
  • 小点是通过 canvas 绘制的,并随机了几个颜色

point.png

const particleContainer = new PIXI.Container();

// 设置容器的中心和 x,y 的位置
    particleContainer.pivot.x = window.innerWidth / 2;
    particleContainer.pivot.y = window.innerHeight / 2;

    particleContainer.x = window.innerWidth / 2;
    particleContainer.y = window.innerHeight / 2;

    particleContainer.rotation = (35 * Math.PI) / 180;  // 容器旋转 35°
    const particles = [];
    const colors = [0xf1cf54, 0xb5cea8, 0xf1cf54, 0x818181, 0x000000];
    let speed = 0;
    for (let index = 0; index < 20; index++) {
      const gr = new PIXI.Graphics();
      const i = Math.floor(Math.random() * colors.length);
      gr.beginFill(colors[i]);
      gr.drawCircle(0, 0, 3);
      gr.endFill();
      gr.x = Math.random() * window.innerWidth;
      gr.y = Math.random() * window.innerHeight;

      const pItem = {
        sx: Math.random() * window.innerWidth,
        sy: Math.random() * window.innerHeight,
        gr,
      };

      particles.push(pItem);
      particleContainer.addChild(gr);
    }

    const loop = () => {
      speed += 0.5;
      speed = Math.min(speed, 20);
      for (let index = 0; index < particles.length; index++) {
        const pItem = particles[index];
        pItem.gr.y += speed;

// 小点的速度为最大值 20 时, 小点变成一条小直线
        if (speed === 20) {
          pItem.gr.scale.x = 0.1;
          pItem.gr.scale.y = 30;
        }
        if (pItem.gr.y > window.innerHeight) {
          pItem.gr.y = 0;
        }
      }
    };
    gsap.ticker.add(loop);
    
  // 把 start 和 pause 两个方法分别添加到actionContainer mouseup 和 mousedown 事件中
    // 松开手刹小点开始移动
    function start() {
      speed = 0;
      gsap.ticker.add(loop);
    }
// 按住手刹小点暂停
    function pause() {
      gsap.ticker.remove(loop);
      for (let index = 0; index < particles.length; index++) {
        const pItem = particles[index];
        pItem.gr.y += speed;
        pItem.gr.scale.x = 1;
        pItem.gr.scale.y = 1;

        gsap.to(pItem.gr, {
          duration: 0.6,
          x: pItem.sx,
          y: pItem.sy,
          ease: "elastic.out",
        });
      }
    }

来看看最终的效果

bike.gif

这只是特效中的一部分。加入我们一起搞事情。