缘起:
在工作之余,经常逛公众号和各种社区的我,发现了一个宝藏公众号 大帅老猿
开启了兼职之旅。
旨在接触更多的真实案例增加自身的业务思考和提升技术视野。
这个动画效果即一个客户案例脱敏后的产物。那么先让我们来看下最终的效果:
动画场景解析
6点下班后,我骑车自行车行驶在空旷的马路上,哼着朴树的平凡之路,想着吉他里和弦的快速切换 4/4 | Em C | G D |。突然 闰土 出现在我面前,递给我一张掘金社区的邀请卡。于是乎我急忙刹住那心爱的凤凰牌自行车,出现了上面动画的场景,刹车。(逃
动画实现解析
前端业务开发,除了特定需求很少有同学会接触到这么复杂的动画实现,看到需求后直接问号三连
就如当初的我一样,总是觉得好难啊。从没接触过这类的业务,无从下手。既然整体看没有结果,那我们就来拆解它。
拆解动画—— 动、画
- 画:我们需要渲染实现如此复杂的图像、交互、视觉效果等那我们需要找一个渲染动画引擎。咦,Pixi.js 就很合适。看下官网介绍:
Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.
- 动:确定了渲染引擎后,那变形动效怎么实现?那就要说起一个
大帅老猿
从小就开始使用的动画引擎库GSAP了,该动画引擎库,历史悠久flash时代就很有名气一直延续至今。
OK,三方库找齐,我们就此开工,创建项目。
项目结构
加载两个库 & 动画文件
// 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);
}
}
在浏览器看到白色画布已经创建
填充图片资源
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,看下效果
自行车已经可以刹车了,就很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);
}
让粒子动起来
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的知识
在公众号里搜 大帅老猿
,在他这里可以学到很多东西