前言
这是 @大帅老猿 老师上个月接的一个私活,这个私活一共要做 6 个动效,工期 15 天(实际用时 7 天),报酬 2w。而今天我们要实现的这个效果就是其中一个稍有难度的。
最终效果
是不是很酷哇?
废话不多说,开始搬砖
html 中引入 PixiJS 和 GSAP
<html>
<head>
<title>PixiJS+GSAP 实现刹车动效</title>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width,minimum-scale=1.0,user-scalable=no"
/>
<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>
<script>
window.onload = () => {
// 这里开始写核心代码
// 后面的代码都写在这里面
}
</script>
</head>
<body>
</body>
</html>
PixiJS 是什么?
- 一个 html5 的一个 2D WebGL 渲染引擎,简单地说就是一个图形渲染库
- 支持交互,可以处理点击和触摸事件
- 是 Flash 的替代品
- 它非常适合网络游戏、教育内容、互动广告、数据可视化......
GSAP 是什么?
- 从 Flash 时代一直发展到今天的老牌的前端专业动画库。
创建一个新的 PIXI.Application 实例
const app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0xffffff,
resizeTo: window,
});
将画布添加到视图
上一步创建的 app 实例上有一个属性 view,这个 view 实际上就是一个 Canvas 元素。
接着,我们将这个元素添加到页面的 Dom 上。
document.body.appendChild(app.view);
加载备用的图片资源
使用资源加载器加载所要需要用到的资源文件
// 创建资源加载器
const loader = new PIXI.Loader();
// 向资源加载器添加资源
loader.add("bg.jpeg", "images/bg.jpeg"); //背景图片
loader.add("btn.png", "images/btn.png"); // 按钮
loader.add("btn_circle.png", "images/btn_circle.png"); // 按钮的外圈(美化用)
loader.add("brake_bike.png", "images/brake_bike.png"); // 车架
loader.add("brake_handlerbar.png", "images/brake_handlerbar.png"); // 车龙头
loader.add("brake_lever.png", "images/brake_lever.png"); // 刹车把
loader.load();
// 加载完成后的回调
loader.onComplete.add(() => {
// 后面的代码都写在这里面
});
绘制背景图片
在 PixiJS 中,绘制图片的方式有很多种,其中最简单的就是使用 Sprite
// 绘制背景图片
const bgImage = new PIXI.Sprite(loader.resources["bg.jpeg"].texture);
// 调整背景图片缩放比例,使其完整清晰(按需调节,图片刚好合适则无需调节)
bgImage.scale.x = 30; // 横轴方式放大 30 倍
bgImage.scale.y = 3; // 横轴方式放大 3 倍
// 将图片放入到 stage (实际也是一个容器`Container`,可以理解为应用的根容器)
app.stage.addChild(bgImage);
绘制控制按钮
每个独立的组件,比如这里的控制按钮就是一个整体,建议通过new PIXI.Container()新建一个容器来存放,而不是直接存入到 stage (根容器),这样做可以方便做整体的调整。
const btnImage = new PIXI.Sprite(loader.resources["btn.png"].texture);
// 新建一个容器来存放这个按钮
const actionButton = new PIXI.Container();
// 这个容器里面可以通过`addChild`来放入子组件
actionButton.addChild(btnImage);
// 调整按钮的坐标位置
actionButton.x = actionButton.y = 300;
// 再将整个按钮容器放入到 stage (根容器)
app.stage.addChild(actionButton);
效果图
绘制自行车
自行车包括三部分:车架、车龙头、刹车手柄。
新建一个容器,把它们放在同一个容器里,方便整体缩放。
// 在场景中创建一个整体容器来存放自行车,方便整体的调整。而不是直接在 stage (根容器)上画
const bikeContainer = new PIXI.Container();
app.stage.addChild(bikeContainer);
// 因自行车图片较大,这里整体缩小下
bikeContainer.scale.x = bikeContainer.scale.y = 0.3;
// 车架
const bikeImage = new PIXI.Sprite(
loader.resources["brake_bike.png"].texture
);
bikeContainer.addChild(bikeImage);
// 刹车手柄
const bikeLeverImage = new PIXI.Sprite(
loader.resources["brake_lever.png"].texture
);
bikeContainer.addChild(bikeLeverImage);
// 车龙头
const bikeHandlerBarImage = new PIXI.Sprite(
loader.resources["brake_handlerbar.png"].texture
);
bikeContainer.addChild(bikeHandlerBarImage);
// 调整刹车手柄到合适的位置
// 中心点的位置
bikeLeverImage.pivot.x = 455;
bikeLeverImage.pivot.y = 455;
// 起点的位置
bikeLeverImage.x = 722;
bikeLeverImage.y = 900;
// 逆时针旋转 12 度
bikeLeverImage.rotation = (Math.PI / 180) * -12;
调整自行车到右下角
因为车体图片不完整,为了美观,使它保持在画面右下角
const resize = () => {
bikeContainer.x = window.innerWidth - bikeContainer.width;
bikeContainer.y = window.innerHeight - bikeContainer.height;
};
window.addEventListener("resize", resize);
resize();
效果图:
实现按钮的交互效果
// `interactive` 属性需设为 `true` ,按钮才可交互
actionButton.interactive = true;
// 让鼠标放上时鼠标呈手指状
actionButton.buttonMode = true;
// 鼠标按下时事件
actionButton.on("mousedown", () => {
gsap.to(bikeLeverImage, {
duration: 0.6,
rotation: (Math.PI / 180) * -30,
});
});
// 鼠标抬起时事件
actionButton.on("mouseup", () => {
gsap.to(bikeLeverImage, {
duration: 0.6,
rotation: (Math.PI / 180) * -12,
});
});
效果图:
实现粒子效果
绘制出不同颜色的多个点,然后让这些点动起来
绘制粒子
// 将这些点存放在对象数组中
const particles = [];
const dots = [
{ x: 10, y: 10, radius: 6, color: 0x000000 },
{ x: 10, y: 200, radius: 6, color: 0x818181 },
{ x: 10, y: 400, radius: 6, color: 0xf1cf54 },
{ x: 210, y: 400, radius: 6, color: 0xb5cea8 },
{ x: 210, y: 10, radius: 6, color: 0xf1cf54 },
{ x: 210, y: 200, radius: 6, color: 0xff0000 },
{ x: 410, y: 400, radius: 6, color: 0xff0000 },
{ x: 410, y: 10, radius: 6, color: 0xff0000 },
{ x: 410, y: 200, radius: 6, color: 0xff0000 },
{ x: 610, y: 400, radius: 6, color: 0xff0000 },
{ x: 610, y: 10, radius: 6, color: 0xff0000 },
{ x: 610, y: 200, radius: 6, color: 0xff0000 },
{ x: 810, y: 400, radius: 6, color: 0xff0000 },
{ x: 810, y: 10, radius: 6, color: 0xff0000 },
{ x: 810, y: 200, radius: 6, color: 0xff0000 },
{ x: 100, y: 100, radius: 6, color: 0xffff00 },
{ x: 100, y: 300, radius: 6, color: 0xffff00 },
{ x: 300, y: 100, radius: 6, color: 0xffff00 },
{ x: 300, y: 300, radius: 6, color: 0xffff00 },
{ x: 500, y: 100, radius: 6, color: 0xffff00 },
{ x: 500, y: 300, radius: 6, color: 0xffff00 },
{ x: 700, y: 100, radius: 6, color: 0xffff00 },
{ x: 700, y: 300, radius: 6, color: 0xffff00 },
];
for (let i = 0; i < dots.length; i++) {
const gr = new PIXI.Graphics();
const item = dots[i];
gr.beginFill(item.color);
gr.drawCircle(0, 0, item.radius);
gr.endFill();
gr.x = item.x;
gr.y = item.y;
particleContainer.addChild(gr);
particles.push({ gr: gr, ...item });
}
效果图:
让粒子在竖直方向上动起来,并且是越来越快,直至达到最大速度
通过gsap.ticker.add();添加一个屏幕渲染时机的钩子,实际上,就是requestAnimationFrame事件
// 初始速度为 0
let speed = 0;
start();
function start() {
// 动起来
speed = 0;
gsap.ticker.add(loop);
}
function loop() {
// 越来越快
speed += 0.5;
// 限制最大速度
speed = Math.min(speed, 20);
for (let i = 0; i < particles.length; i++) {
const item = particles[i];
const gr = item.gr;
// 在y轴方向上动起来
gr.y += speed;
// 超出底部边界后回到顶部继续移动
if (gr.y > window.innerHeight) {
gr.y = 0;
}
}
}
效果图:
让粒子朝一个斜角方向移动
简单!只需要旋转容器到一定角度即可
// 改变容器的中心点为页面的中心
particleContainer.x = window.innerWidth / 2;
particleContainer.y = window.innerHeight / 2;
particleContainer.pivot.x = particleContainer.x;
particleContainer.pivot.y = particleContainer.y;
// 顺时针转 35 度
particleContainer.rotation = (35 * Math.PI) / 180;
让粒子有速度线的视觉效果
其实就是让粒子拉长、变细。
也就是让粒子在 x 方向上缩小,在 y 方向上放大。
修改 loop 函数:
function loop() {
// 越来越快
speed += 0.5;
// 限制最大速度
speed = Math.min(speed, 20);
for (let i = 0; i < particles.length; i++) {
const item = particles[i];
const gr = item.gr;
// 在y轴方向上动起来
gr.y += speed;
// 速度达到某个值时,制造出速度线效果
if (speed > 19) {
gr.scale.x = 0.05;
gr.scale.y = 40;
}
// 超出底部边界后回到顶部继续移动
if (gr.y > window.innerHeight) {
gr.y = 0;
}
}
}
控制粒子的运动
按钮按下时停止运动,按钮松开时重新开始
// 鼠标按下时事件
actionButton.on("mousedown", () => {
gsap.to(bikeLeverImage, {
duration: 0.6,
rotation: (Math.PI / 180) * -30,
});
pause();
});
// 鼠标抬起时事件
actionButton.on("mouseup", () => {
gsap.to(bikeLeverImage, {
duration: 0.6,
rotation: (Math.PI / 180) * -12,
});
start();
});
function pause() {
// 先移除掉 requestAnimationFrame 的侦听
gsap.ticker.remove(loop);
for (let i = 0; i < particles.length; i++) {
const item = particles[i];
const gr = item.gr;
// 恢复小圆点的拉伸比例
gr.scale.x = 1;
gr.scale.y = 1;
// 让所有的小圆点使用弹性补间动画回到初始坐标
gsap.to(gr, {
duration: 0.6,
x: item.x,
y: item.y,
ease: "elastic.out",
});
}
}
效果图:
美化按钮
最后,我们来美化下按钮,给按钮加上一个外圈,并且加上波纹动效。
- 绘制两个圆圈
- 让其中一个圆圈从小到大无限循环
beautifyButton();
function beautifyButton() {
// 绘制两个圆圈
const btnCircle = new PIXI.Sprite(
loader.resources["btn_circle.png"].texture
);
const btnCircle2 = new PIXI.Sprite(
loader.resources["btn_circle.png"].texture
);
actionButton.addChild(btnCircle);
actionButton.addChild(btnCircle2);
// 改变中心点
btnImage.pivot.x = btnImage.width / 2;
btnImage.pivot.y = btnImage.height / 2;
btnCircle.pivot.x = btnCircle.width / 2;
btnCircle.pivot.y = btnCircle.height / 2;
btnCircle2.pivot.x = btnCircle2.width / 2;
btnCircle2.pivot.y = btnCircle2.height / 2;
// 让其中一个圆圈从小到大无限循环
btnCircle.scale.x = btnCircle.scale.y = 0.8;
gsap.to(btnCircle.scale, {
duration: 1,
x: 1.2,
y: 1.2,
repeat: -1,
});
gsap.to(btnCircle, { duration: 1, alpha: 0, repeat: -1 });
}
大功告成~
小结
在公众号里搜 大帅老猿,在他这里可以学到很多东西