一文带你入门动效制作|Gasp+PIXI制作刹车动效| 猿创营

837 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

前言

阿伟是个前端工程师,虽然才入门一两年,但是不论是什么xx管理还是什么xx数据分析他都已经久禁磨砺、了然于胸。

多少也是常打交道的东西,我不熟但是百度和他熟啊(阿伟心想)

但是这样的阿伟却碰到了个麻烦,公司最近接了一个活,完成一个官网介绍项目,阿伟心想官网来回无非就是搞搞布局排版,于是也就爽快的答应了下来。但是当看到需求的时候他却傻了眼(ps:真做项目一切都要等需求和价格敲定好再答应,这里是剧情需要)

需求1:完成官网内容制作 可参照www.vanmoof.com/en-NL/s3?co…

需求2:页面美观大气,在不同设备上能够正常工作且有较好展示效果

需求3:xxx ……

阿伟想着先看看又要抄什么竞品打开了这个网站,向下一滑,他就傻了眼。

官网效果.gif 这有点酷炫啊!但是,这我不会啊! 平常开心快乐的阿伟一下就犯了难,这个用啥做呀,又开启了照妖镜(网站开发者工具F12)想看他个究竟(妖孽,我要你现出原形)。 借助元素查找工具,他发现那里是几个div,再和css一看他发现了一个网址

image.png

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使用


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