记一次通过PIXI.js 和 Gasp 实现动画特效获取3K时薪的体验(附源码) | 猿创营

3,621 阅读4分钟

缘起:

在工作之余,经常逛公众号和各种社区的我,发现了一个宝藏公众号 大帅老猿 开启了兼职之旅。 旨在接触更多的真实案例增加自身的业务思考和提升技术视野。

这个动画效果即一个客户案例脱敏后的产物。那么先让我们来看下最终的效果:

Untitled1.gif

动画场景解析

6点下班后,我骑车自行车行驶在空旷的马路上,哼着朴树的平凡之路,想着吉他里和弦的快速切换 4/4 | Em C | G D |。突然 闰土 出现在我面前,递给我一张掘金社区的邀请卡。于是乎我急忙刹住那心爱的凤凰牌自行车,出现了上面动画的场景,刹车。(逃

动画实现解析

前端业务开发,除了特定需求很少有同学会接触到这么复杂的动画实现,看到需求后直接问号三连

image.png image.png

就如当初的我一样,总是觉得好难啊。从没接触过这类的业务,无从下手。既然整体看没有结果,那我们就来拆解它。

拆解动画—— 动、画

  • :我们需要渲染实现如此复杂的图像、交互、视觉效果等那我们需要找一个渲染动画引擎。咦,Pixi.js 就很合适。看下官网介绍:

Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.

  • :确定了渲染引擎后,那变形动效怎么实现?那就要说起一个大帅老猿从小就开始使用的动画引擎库GSAP了,该动画引擎库,历史悠久flash时代就很有名气一直延续至今。

OK,三方库找齐,我们就此开工,创建项目。

项目结构

image.png

加载两个库 & 动画文件

// index.html
<head>
    ...
    <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>

    // 加载动画js文件
    <script src="./js/brakebanner.js"></script>
    ...
</head>

添加加载逻辑

// index.html
<head>
    ...
    <script>
        window.onload = init;

        function init() {
            let banner = new BrakeBanner("#brakebanner");
        }
    </script>
    ...
</head>
<body>
    <div id="brakebanner"></div>
</body>

到此 html文件就写完了,下面看下需要执行的js文件的具体写法。均在 brakebanner.js 文件编写

创建一个应用 PIXI.Application

class BrakeBanner{
    constructor(selector){
        // 初始化应用
        this.app = new PIXI.Application({
            with: window.innerWidth,
            height: window.innerHeight,
            backgroundColor: 0xffffff,
            resizeTo: window
        })
        this.stage = this.app.stage;
        document.querySelector(selector).appendChild(this.app.view);
    }
}

在浏览器看到白色画布已经创建

image.png

填充图片资源

this.loader = new PIXI.Loader();
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 actionButoon = new PIXI.Container();
this.stage.addChild(actionButoon);

// 创建自行车容器
let bikeContainer = new PIXI.Container();
this.stage.addChild(bikeContainer);
// -----按钮处理 start ---------
// 生成对应的资源
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);
         
actionButoon.addChild(btnImage);
actionButoon.addChild(btnCircle);
actionButoon.addChild(btnCircle2);

// 圆圈居中
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;
gsap.to(btnCircle.scale, { duration: 1, x: 1.3, y: 1.3, repeat: -1})
gsap.to(btnCircle, { duration: 1, alpha: 0, repeat: -1})

// -------- 按钮处理 end --------------

// -------- 自行车处理 start -----------
bikeContainer.scale.x = bikeContainer.scale.y = 0.3;

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

let bikeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture);
bikeContainer.addChild(bikeLeverImage);

bikeLeverImage.pivot.x = bikeLeverImage.pivot.y = 455;
bikeLeverImage.x = 722;
bikeLeverImage.y = 900;

let bikeHandleImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);
bikeContainer.addChild(bikeHandleImage);

// --------- 自行车处理 end -----------

OK,看下效果

Untitled2.gif 自行车已经可以刹车了,就很nice!效果图上还有骑车后的速度显示粒子,在视觉上感觉自行车是在跑动的。我们来继续实现下。

实现粒子容器

 // 创建粒子
let particleContainer = new PIXI.Container();
this.stage.addChild(particleContainer);
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;

创建不同的粒子

let particles = [];
let colors = [0xff0000, 0xb3cea8, 0xf1cf54, 0x8100aa]
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();
    const pItem = {
        sx: Math.random() * window.innerWidth,
        sy: Math.random() * window.innerHeight,
        gr: gr
    }
    gr.x = pItem.sx;
    gr.y = pItem.sy;

    particleContainer.addChild(gr);

    particles.push(pItem);
}

image.png

让粒子动起来

let speed = 0;
const loop = () => {
    // 速度递增至最大速度
    speed += 0.5;
    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;
            pItem.gr.scale.x = 0.03;
        }

        // 运动出屏幕后回到起点
        if(pItem.gr.y > window.innerHeight) pItem.gr.y = 0;
    }
}

跑起来了,跑起来了。最后我们增加事件绑定。让按钮按下时粒子停止运动,松开后粒子重新运动

绑定事件

 actionButoon.on('mousedown', ()=> {
    gsap.to(bikeLeverImage, { duration: 0.6, rotation: Math.PI/180*-30});
    pause();
});
actionButoon.on('mouseup', ()=> {
    gsap.to(bikeLeverImage, { duration: 1, rotation: 0});
    start();
});

芜湖~~ 完成! 一个小时不到 我们就完成了动画的效果,顺便还能拿到酬金,是不是很nice。

完整源码

感谢读到此处的你们,感谢掘金提供这个可以分享知识的平台。本人在练习技术写作,总结自身的一些经验和学习到的新技术!期待你的关注、点赞和转发! 如果你想接触更多的动画实现以及three.js的知识

公众号里搜 大帅老猿,在他这里可以学到很多东西