背景
本篇文章来源于大帅老猿摸金校尉群(一个教你接私活赚外快的群,感兴趣的可以联系大帅老猿)内的一次全员实战项目,是一个真实的外包项目,在交付给客户之后经过大帅的脱敏之后,给群内小伙伴练手学习的,对于和我一样从未学习过前端动效开发的小白来说,是一个不错的练手项目。
客户需求
简单来说,就是要完成类似 vanmoof 电商页面的刹车视觉动效(链接:www.vanmoof.com/en-NL/s3?co… ),效果如下图所示:
这种效果要让没有写过动效的小伙伴来写,可能完全不知道从何下手,但是跟着下面的步骤做,你就会发现其实还好,也是蛮有意思的。下面上干货!
需求分析
组成元素
首先,上图中大体上由四个部分组成:
- 按钮
- 车架及车把
- 刹车
- 动线
动效
动效主要有三个:
- 按钮点击的波纹效果
- 刹车的按压效果
- 动线的效果及按下刹车时动线的回弹效果
交互
交互总共就一个:
- 点击按钮时的动效的变化
分析完需求,下面正式进入代码的开发。
开发环境配置
本次项目开发环境如下:
- IDE:VS Code
- VS Code 插件:微软出品的 Live Preview,方便开发过程中效果的预览
- JS 库:PIXI、GSAP
- 项目基础代码:github.com/ezshine/YCY…
上面的开发环境配置好后,从基础代码中fork一份出来,然后把代码取到本地,并用 VS Code 打开项目。 项目结构如下图所示:
结构很简单,主要包括要用到的图片资源,动效核心代码 brakebanner.js,以及 index.html (引入了 PIXI 及 GSAP),其他的文件不用关心,详细的代码可以去基础代码仓库中查看。
用 VS Code 打开项目之后,我们直接打开 brakebanner.js 文件,并在 index.html 文件上右键 Live Preview: Show Preview,使用 Live Preview 插件预览项目,此时,你看到的页面应该是空白的,像下面这样:
不要慌,此时项目正常运行的结果就是这样!
第一步,先创建一块画布
在我们正式画内容之前,我们要先创建一块画布,然后再在画布上画我们要的效果,下面是代码:
this.app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0xffffff
});
document.querySelector(selector).appendChild(this.app.view);
代码运行结果如下:
蛤?怎么这样?怎么啥都没有?你TM在逗我?浪费时间啊同志!
不要急,因为此时画布的背景色是白色的,看不出代码的效果,我们先把背景色改成红色的,效果就出来了。
恭喜你!你已经成功通过 PIXI 的 API 在画面上创建了一块红色的画布!入门了,入门了!
下面,我们一个接一个地画前面需求分析中提到的几个元素,首先是按钮:
第二步,绘制按钮及第一个动效
// 通过 Loader 加载图片资源
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.load();
// 在 Loader 的成功回调里绘制按钮
this.loader.onComplete.add(() => {
// 创建一个容器,有助于容器内资源整体的控制
let actionButton = new PIXI.Container();
let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
let btnCircle = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
// 容器里加入按钮图片及圆环
actionButton.addChild(btnImage);
actionButton.addChild(btnCircle);
// 将容器添加到画布上,此时就能在画布上看到按钮了
this.app.stage.addChild(actionButton);
});
效果如下:
此时,按钮已经出来了,下面我们调整按钮与圆环的位置,并为按钮加上动态效果:
// 调整按钮位置
actionButton.x = actionButton.y = 300;
btnImage.pivot.x = btnImage.pivot.y = btnImage.width / 2;// 通过 pivot 对象调整元素的圆心位置
btnCircle.pivot.x = btnCircle.pivot.y = btnCircle.width / 2;
// 添加按钮圆环动效
btnCircle.scale.x = btnCircle.scale.y = 0.8;
gsap.to(btnCircle.scale, { duration: 1, x: 1.3, y: 1.3, repeat: -1 });
效果如图:
再对其进行微调:
let btnCircle2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
actionButton.addChild(btnCircle2);// 再加一个圆环
gsap.to(btnCircle, { duration: 1, alpha: 0, repeat: -1 });// 加点透明度的变化
最终效果如下:
怎么样,是不是感觉已经像模像样了呢?
此时,通过 GSAP,我们成功地给按钮的圆环添加了波纹的动效,第一个动效大功告成!
至此,本文我们要用到的 API 基本展示完毕,总结一下,就是以下几个 API:
// PIXI
PIXI.Application
pixiApplication.view
pixiApplication.stage // 根显示容器,也可以理解为画布
// 加载资源
PIXI.Loader
Loader.add
Loader.load
Loader.onComplete.add
Loader.resources
// 创建容器,组合元素
PIXI.Container
PIXI.Container.addChild
// 精灵,可以渲染图像
PIXI.Sprite
// Sprite、Container 都是 DisplayObject
DisplayObject.x
DisplayObject.y
DisplayObject.scale
DisplayObject.pivot
// GSAP
gsap.to
下面我们就围绕这上面用到的这些 API完成剩下的绘制工作!
第三步,绘制车架、车把及刹车
// 加载车架、车把及刹车图片资源
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');
// 创建自行车容器
let bikeContainer = new PIXI.Container();
this.app.stage.addChild(bikeContainer);
// 自行车图片太大,缩小一下图片
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;
// 车把:车把要盖在刹车上,所以车把手要在刹车后面添加,PIXI 是以元素添加的顺序定元素的显示层级的
let bikeHandlerbarImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);
bikeContainer.addChild(bikeHandlerbarImage);
// 将自行车始终保持在画布的右下角位置
let resize = () => {
bikeContainer.x = window.innerWidth - bikeContainer.width;
bikeContainer.y = window.innerHeight - bikeContainer.height;
};
window.addEventListener('resize', resize)
resize();
效果如图:
第四步,添加按钮交互事件
按钮与自行车都画好了,这个时候就该给按钮添加交互效果了:
// 启用按钮的交互事件
actionButton.interactive = true;
// 光标移动到按钮上时显示“小手”
actionButton.buttonMode = true;
actionButton.on('mousedown', () => {
gsap.to(bikeLeverImage, { duration: .6, rotation: -30 * Math.PI / 180 });
})
actionButton.on('mouseup', () => {
gsap.to(bikeLeverImage, { duration: .6, rotation: 0 });
})
效果如图:
如图,通过 PIXI 元素的 mousedown、mouseup 事件以及 gsap 的 to 方法,我们实现了长按左键按下刹车,放开左键松开刹车的效果。
第五步,绘制动线效果
下面进行本次动效中最难的一步,完成动线的绘制:
let particleContainer = new PIXI.Container();
this.app.stage.addChild(particleContainer);
// 通过旋转容器实现动线向左下角移动的效果
particleContainer.rotation = 35 * Math.PI / 180;
// 旋转后,对位置进行适当的调整
particleContainer.pivot.x = window.innerWidth / 2;
particleContainer.pivot.y = window.innerHeight / 2;
particleContainer.x = window.innerWidth / 2;
particleContainer.y = window.innerHeight / 2;
// 随机生成 10 个圆点
let particles = [];
let colors = [0xf1cf54, 0xb5cea8, 0xf1cf54, 0x81881, 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 pItem = {
sx: Math.random() * window.innerWidth,
sy: Math.random() * window.innerHeight,
gr
};
gr.x = pItem.sx;
gr.y = pItem.sy;
particleContainer.addChild(gr);
particles.push(pItem);
}
// 速度由 0 增加到 20
let speed = 0;
function loop() {
speed += .5;
speed = Math.min(speed, 20);
for (let i = 0; i < particles.length; i++) {
let pItem = particles[i];
pItem.gr.y += speed;
// 速度增加到 20 了才变成线,实现一个由静到动的过渡效果
if (speed >= 20) {
pItem.gr.scale.y = 40;
pItem.gr.scale.x = 0.03;
}
// 圆点运动到底部了,再从回到顶部,实现一个循环的效果
if (pItem.gr.y > window.innerHeight) {
pItem.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];
pItem.gr.scale.y = 1;
pItem.gr.scale.x = 1;
// 通过 ease 实现回弹的效果
gsap.to(pItem.gr, { duration: .6, x: pItem.sx, y: pItem.sy, ease: 'elastic.out' });
}
}
start();
// 按住按钮,刹车的同时,动线停止运动,并有一个回弹的效果
actionButton.on('mousedown', () => {
pause();
})
// 松开按钮,动线继续运动
actionButton.on('mouseup', () => {
start();
})
效果如下图所示:
代码注释已给出,请参照注释及效果图理解动效的实现过程,此处用到了一个新的 api,gsap.ticker。
至此,全部动效都已实现完毕。
总结
本文主要通过 PIXI 实现了图片的绘制、定位及旋转,还有圆点的绘制、变形,然后配合 GSAP 的 to 方法及 ticker 方法实现我们想要的动效,用到的 API 其实不多。
是不是感觉前端动效“也不过如此”?那么,还等什么,赶快也自己动手去实现一个吧!
最后
在公众号里搜 大帅老猿,在他这里可以学到很多东西!