携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
前言
阿伟是个前端工程师,虽然才入门一两年,但是不论是什么xx管理还是什么xx数据分析他都已经久禁磨砺、了然于胸。
多少也是常打交道的东西,我不熟但是百度和他熟啊(阿伟心想)
但是这样的阿伟却碰到了个麻烦,公司最近接了一个活,完成一个官网介绍项目,阿伟心想官网来回无非就是搞搞布局排版,于是也就爽快的答应了下来。但是当看到需求的时候他却傻了眼(ps:真做项目一切都要等需求和价格敲定好再答应,这里是剧情需要)
需求1:完成官网内容制作 可参照www.vanmoof.com/en-NL/s3?co…
需求2:页面美观大气,在不同设备上能够正常工作且有较好展示效果
需求3:xxx ……
阿伟想着先看看又要抄什么竞品打开了这个网站,向下一滑,他就傻了眼。
这有点酷炫啊!但是,这我不会啊!
平常开心快乐的阿伟一下就犯了难,这个用啥做呀,又开启了照妖镜(网站开发者工具F12)想看他个究竟(妖孽,我要你现出原形)。
借助元素查找工具,他发现那里是几个div,再和css一看他发现了一个网址
vanmoof-website-cdn.s3.eu-central-1.amazonaws.com/v1/assets/p…
阿伟感觉他找到了眉头,正要开心,打开一看发现是一个静态图片,这下阿伟彻底崩了。 冥思苦想无果之下,阿伟找到了群内大佬大帅老猿
大帅老猿简单看了下,说:“你之前有用过动效库吗,或者你有用过canvas或者svg制作过动态效果吗”
阿伟苦笑着回到 “我哪会这些啊,看到这些我都头大,老鼠拉龟——无从下手啊这”
大帅老猿想了想说正好群里小伙伴好多都没弄过,干脆就教教你们吧
先上结果
正片
前期准备工作
“这次我们会用到两个库一个是Gasp,一个老牌的动画引擎,另一个是PIXI,一个一个非常快的2D sprite渲染引擎,对应的链接就放在下面了,你们可以去他们官网看看”
(这里给的是中文网站,还不是对应的官网哦,只是为了方便查看) pixijs.huashengweilai.com/ www.tweenmax.com.cn/index.html
GSAP 英文文档 greensock.com/docs/
创建基础页面
首先呢,咱们先创建一个容器,就跟你绘画一样,你总得有有一个画布是吧,你不能拿着你珍贵的颜料就往地上画了,先不说会不会被罚款,这改也不好改啊。
在线调试容器
代码
class BrakeBanner{
constructor(selector){//接受一个dom
this.app = new PIXI.Application({
width:window.innerWidth,
height:window.innerHeight,
backgroundColor:0x00ffff,//随便给个底色
})//创建一个应用
this.stage = this.app.stage;//创建一个画布
document.querySelector(selector).appendChild(this.app.view);//添加到该dom子级
}
}
function init() {
let banner = new BrakeBanner("#brakebanner");
}
init();
资源的添加与使用
目前这个容器是创建出来了,但是除了我们初始化时给的颜色,其他什么也没有,所以接下来我们添加一点元素进去
this.loader = new PIXI.Loader();//创建资源加载器
this.loader.add('btn.png', 'images/btn.png');//先添加资源 loader,add(name,'path')
this.loader.add('btn_circle.png', 'images/btn_circle.png');
this.loader.load()//加载
this.loader.onComplete.add(() => {//加载完毕触发事件
let actionButton = new PIXI.Container();//初始化一个容器
this.stage.addChild(actionButton);//将容器加载到stage中
const btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture); //加载完毕后获得资源
actionButton.addChild(btnImage); //添加到容器中
const btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
actionButton.addChild(btnCircleImage);
})
实际操作中我将资源加载获取封装了一下
PIXI.Sprite.from(path);//直接从资源路径获取元素
addSouce(basePath, souceArray = this.souceArray) {//添加资源
for (const name of souceArray) {
this.loader.add(name, `${basePath}/${name}`);
}
}
getResources(name) {//获得元素内容
return new PIXI.Sprite(this.loader.resources[name].texture);
}
添加按钮
createActionButton() {
let actionButton = new PIXI.Container();//初始化一个容器
this.stage.addChild(actionButton);//将容器加载到stage中
let btnImage = this.getResources('btn.png');
let btnCircle = this.getResources('btn_circle.png');
let btnCircle2 = this.getResources('btn_circle.png');
actionButton.addChild(btnImage);//获得资源并加载
actionButton.addChild(btnCircle);
actionButton.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;//设定初始缩放为0.8;
btnCircle2.scale.x = btnCircle2.scale.y = 0.8//设定初始缩放为0.8;
gsap.to(btnCircle.scale, { duration: 1, x: 1.3, y: 1.3, repeat: -1 });//设置动画为放大倍速到1.3并且无限重复
gsap.to(btnCircle2.scale, { duration: 1, x: 1, y: 1, repeat: -1 });//设置动画为放大倍速到1.3并且无限重复
gsap.to(btnCircle, { duration: 1, alpha: 0, repeat: -1 });//设置动画为逐渐消失
return actionButton
}
给大家详细解一下上面的代码
btnImage.pivot.x = btnImage.pivot.y = btnImage.width / 2;//使中心点从左上角更改为中心
元素被加载进来的时候默认是在左上角,他们的定位点也是根据左上角来定位的,那么对于这个正方形的一张图片,将原点设置为宽高一半更有利于位置的调整
gsap.to(btnCircle.scale, { duration: 1, x: 1.3, y: 1.3, repeat: -1 });//设置动画为放大倍速到1.3并且无限重复
这里用到了一个gsap的to方法 意为从当前状态到指定状态 duration为持续时间 repeat为次数 就像你有一个给自己加速的技能 1分钟内逐渐提升到130%的速度 cd为0一样的
给按钮绑定点击事件
actionButton.interactive = true;
actionButton.buttonMode = true;//更改cursor 亦可借助actionButton.cursor = 'wait';更改
actionButton.on('pointerdown', (e) => {//桌面端移动端兼容的鼠标按下事件 桌面端是mousedown和mouseup
gsap.to(this.bikelever, { duration: 0.4, rotation: Math.PI / 180 * -35, repeat: 0 });
gsap.to(bike, { duration: 0.3, x: bike.x - 20, y: bike.y + 20, repeat: 0 });
particles.pause();
road.pause();
})
actionButton.on('pointerup', (e) => {//桌面端移动端兼容的鼠标抬起事件
gsap.to(this.bikelever, { duration: 0.4, rotation: 0, repeat: 0 });
gsap.to(bike, { duration: 0.3, x: bike.x + 20, y: bike.y - 20, repeat: 0 });
particles.start();
road.start()
})
添加单车
createBike() {
const bikeContainer = new PIXI.Container();
this.stage.addChild(bikeContainer);
bikeContainer.scale.x = bikeContainer.scale.y = 0.3;
let bikeimage = this.getResources('brake_bike.png');
let bikeHandbar = this.getResources('brake_handlerbar.png');
let bikelever = this.getResources('brake_lever.png');
this.bikelever = bikelever;//刹车挂载到this上
bikeimage.tint = 0x05AFF2; // 给自行车着色 需要注意的是,着色是在原色彩基础上混合颜色而不是覆盖颜色
bikeHandbar.tint = 0x94E1F2;
bikelever.tint = 0x0442BF;
bikelever.pivot.x = bikelever.pivot.y = 455;
bikelever.x = 772;
bikelever.y = 900;
bikeContainer.addChild(bikeimage)
bikeContainer.addChild(bikelever)//先添加以显示在内部
bikeContainer.addChild(bikeHandbar)
return bikeContainer
}
这里就是和之前一样了,创建容器=>添加容器=>添加元素
bikeimage.tint = 0x05AFF2; 设置元素的tint属性可以为其换个炫彩 可以设置 0~1 * 0xFFFFFF观察这个的影响
这里出现了一个层级问题,如果不注意的话,你会发现把手是暴露在外面的
这是因为他作为以canvas实现的库,遵循canvas默认的后来居上规则,所以我们先添加车把手再添加车杆就不会穿帮啦
添加轨迹线特效
上面我们只是把素材都加入进了画布,并且做了简单的一个整合,但是他依然没有一个运动的感觉,下面我们将通过改变页面元素的方式来打造这个感觉
先绘制圆形
let gr = new PIXI.Graphics();//创建一个画笔实例,类似canvas的context对象
gr.beginFill(color[Math.floor(Math.random() * color.length)]);//开始绘制并设置画笔颜色
gr.drawCircle(0, 0, 6)//绘制圆形
gr.endFill();//结束绘制
let pitem = {
sx: i * 0.08 * window.innerWidth,
sy: Math.random() * window.innerHeight,
gr: gr
}//对象存储绘制的圆和随机一个出生点
gr.x = pitem.sx,
gr.y = pitem.sy;
container.addChild(gr);
particles.push(pitem)
再创建圆形的事件
let speed = 0;//初始化速度
function loop() {
speed += .5;//速度逐渐加快
speed = Math.min(speed, 20)//速度设置上线20
for (let i = 0; i < particles.length; i++) {
const element = particles[i];
element.gr.y += speed;
if (speed >= 20) {
element.gr.scale.y = 40;
element.gr.scale.x = 0.03;
}//动态改变圆的位置并降低圆的大小 缓慢滚动的大圆球=>快速冲刺的小圆点=>线 真是奇妙
if (element.gr.y > window.innerHeight) element.gr.y = 0//当点溢出屏幕把他抓回来
}
}
container.pivot.x = container.x = window.innerWidth / 2;
container.pivot.y = container.y = window.innerHeight / 2;
container.rotation = 32 * Math.PI / 180;//通过修改容器的方向,使原本上下的原点变成斜向运动
function start() {
speed = 0;//开始速度
gsap.ticker.add(loop);//绑定事件循环调用
}
function pause() {
gsap.ticker.remove(loop);//清除事件绑定
for (let i = 0; i < particles.length; i++) {
const element = particles[i];
element.gr.scale.y = 1;//还原大小
element.gr.scale.x = 1;
gsap.to(element.gr, { duration: .5, x: element.sx, y: element.sy, ease: 'elastic.out' });//缓出恢复初始位置 因为直接设置位置会显得较为死板,加上缓出后会更有运动感
}
}
start();
return {
pause,
start
}//将暂停和启动方法暴露给外部
}
添加道路
这里本质上和之前是类似的,只是由于道路是一张大图,所以我将他放大处理企图让自行车行进在道路上
roadContainer() {
let container = new PIXI.Container();
this.stage.addChild(container);
let roadImage = this.getResources('malu.png');
container.addChild(roadImage)
container.pivot.x = window.innerWidth / 2;
container.pivot.y = window.innerHeight / 2;
let sy = container.y = -800;
let sx = container.x = 1000;
container.scale.x = container.scale.y = 1.45
//旋转位置使其与车轮平行
container.rotation = 35 * Math.PI / 180;
let speed = 0
function Rloop() {
speed += .5;
speed = Math.min(speed, 20)
//计算改变Y
container.y += (Math.cos(35 * Math.PI / 180)) * speed;
//计算改变X
container.x -= (Math.sin(35 * Math.PI / 180)) * speed;
//超出重置
if (container.y > 60) {
container.y = sy, container.x = sx;
}
}
function start() {
speed = 0;
gsap.ticker.add(Rloop);
}
function pause() {
gsap.ticker.remove(Rloop);
gsap.to(container, { duration: .5, x: container.x += (Math.sin(35 * Math.PI / 180)) * speed , y: container.y -= (Math.cos(35 * Math.PI / 180)) * speed, ease: 'elastic.out' });
}
start();
return {
pause,
start
}
}
最后附上源码地址,各位可以fork使用
在公众号里搜 大帅老猿
,在他这里可以学到很多东西