likes
comments
collection
share

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

作者站长头像
站长
· 阅读数 24

环境搭建

通过代码仓库获取项目素材

最终效果

先来看下最终效果

技术选型

通过PIXI绘制2d图像,使用gsap实现动画效果

初始化画布

首先通过PIXI初始化画布

this.app = new PIXI.Application({
    width: window.innerWidth,
    height: window.innerHeight,
    backgroundColor: 0x0000ff,
    resizeTo: window
})

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

创建资源加载器

通过PIXI的资源加载器加载资源

this.loader = new PIXI.Loader();
this.loader.add('btn.png', 'images/btn.png');
this.loader.load();

this.loader.onComplete.add(()=>{
  const btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture); 
  this.app.stage.addChild(btnImage);
});

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

优化资源加载器

我们可以将加载按钮资源通过show方法提到外面去

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		document.querySelector(selector).appendChild(this.app.view);

		// 创建资源加载器
		this.loader = new PIXI.Loader();
		this.loader.add('btn.png', 'images/btn.png');
		this.loader.load();

		this.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {
		let actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		const btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		this.stage.addChild(btnImage);
	}
}

加载按钮外圈资源并更改位置

将按钮的资源加入到按钮组图层中,这样就可以更好的控制按钮的位置了

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.load();

		this.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {
		let actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2
		actionButton.x = actionButton.y = 200;
	}
}

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

使用gsap实现动画效果

使用gsap优化过渡效果

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.load();

		this.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {
		let actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2
		actionButton.x = actionButton.y = 200;

		// 使用gsap实现动画效果
		btnCircleImage.scale.x = btnCircleImage.scale.y = 0.8
		gsap.to(btnCircleImage.scale, { duration: 1, x: 1.2, y: 1.2, repeat: -1 })
		gsap.to(btnCircleImage.scale, { duration: 1, alpha: 0, repeat: -1 })
	}

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

简单优化代码结构

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.load();

		this.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {
		let actionButton = this.createActionButton()
		actionButton.x = actionButton.y = 200;
	}
	createActionButton() {
		// 创建按钮图层
		let actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2


		// 使用gsap实现动画效果
		btnCircleImage.scale.x = btnCircleImage.scale.y = 0.8
		gsap.to(btnCircleImage.scale, { duration: 1, x: 1.2, y: 1.2, repeat: -1 })
		gsap.to(btnCircleImage.scale, { duration: 1, alpha: 0, repeat: -1 })
		return actionButton
	}
}

加载自行车元素

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {


		// 创建自行车图层
		const bikeContainer = new PIXI.Container();
		this.stage.addChild(bikeContainer);

		bikeContainer.scale.x = bikeContainer.scale.y = 0.3

		const brakeBikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture);
		const brakeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture);
		const brakeHandlerbarImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);

		bikeContainer.addChild(brakeBikeImage);
		bikeContainer.addChild(brakeLeverImage);
		bikeContainer.addChild(brakeHandlerbarImage);

		// 调整车闸的位置
		brakeLeverImage.pivot.x = 455
		brakeLeverImage.pivot.y = 455
		brakeLeverImage.x = 722
		brakeLeverImage.y = 900

		// 按钮图层
		let actionButton = this.createActionButton()
		actionButton.x = actionButton.y = 400;

	}
	createActionButton() {
		// 创建按钮图层
		const actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2


		// 使用gsap实现动画效果
		btnCircleImage.scale.x = btnCircleImage.scale.y = 0.8
		gsap.to(btnCircleImage.scale, { duration: 1, x: 1.2, y: 1.2, repeat: -1 })
		gsap.to(btnCircleImage.scale, { duration: 1, alpha: 0, repeat: -1 })
		return actionButton
	}
}

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

按钮与车闸的交互

通过PIXIon方法添加按钮与车闸之间的交互

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {


		// 创建自行车图层
		const bikeContainer = new PIXI.Container();
		this.stage.addChild(bikeContainer);

		bikeContainer.scale.x = bikeContainer.scale.y = 0.3

		const brakeBikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture);
		const brakeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture);
		const brakeHandlerbarImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);

		bikeContainer.addChild(brakeBikeImage);
		bikeContainer.addChild(brakeLeverImage);
		bikeContainer.addChild(brakeHandlerbarImage);

		// 调整车闸的位置
		brakeLeverImage.pivot.x = 455
		brakeLeverImage.pivot.y = 455
		brakeLeverImage.x = 722
		brakeLeverImage.y = 900

		// 按钮图层
		let actionButton = this.createActionButton()
		actionButton.x = actionButton.y = 400;

		// 按钮与车闸的交互
		actionButton.interactive = true
		actionButton.buttonMode = true
		actionButton.on("mousedown", () => {
			// brakeLeverImage.rotation = Math.PI / 180 * -30
			gsap.to(brakeLeverImage, { duration: .8, rotation: Math.PI / 180 * -30 })
		})
		actionButton.on("mouseup", () => {
			gsap.to(brakeLeverImage, { duration: .6, rotation: 0 })
		})

	}
	createActionButton() {
		// 创建按钮图层
		const actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2


		// 使用gsap实现动画效果
		btnCircleImage.scale.x = btnCircleImage.scale.y = 0.8
		gsap.to(btnCircleImage.scale, { duration: 1, x: 1.2, y: 1.2, repeat: -1 })
		gsap.to(btnCircleImage.scale, { duration: 1, alpha: 0, repeat: -1 })
		return actionButton
	}
}

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

创建骑行粒子

使用for创建粒子,粒子特点为多颜色,固定角度一直运动,按钮按住时停止运动并由回弹效果

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {
		// 创建自行车图层
		const bikeContainer = new PIXI.Container();
		this.stage.addChild(bikeContainer);

		bikeContainer.scale.x = bikeContainer.scale.y = 0.2

		const brakeBikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture);
		const brakeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture);
		const brakeHandlerbarImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);

		bikeContainer.addChild(brakeBikeImage);
		bikeContainer.addChild(brakeLeverImage);
		bikeContainer.addChild(brakeHandlerbarImage);

		// 调整车闸的位置
		brakeLeverImage.pivot.x = 455
		brakeLeverImage.pivot.y = 455
		brakeLeverImage.x = 722
		brakeLeverImage.y = 900

		// 按钮图层
		let actionButton = this.createActionButton()
		actionButton.x = actionButton.y = 400;

		// 按钮与车闸的交互
		actionButton.interactive = true
		actionButton.buttonMode = true
		actionButton.on("mousedown", () => {
			// brakeLeverImage.rotation = Math.PI / 180 * -30
			gsap.to(brakeLeverImage, { duration: .8, rotation: Math.PI / 180 * -30 })
		})
		actionButton.on("mouseup", () => {
			gsap.to(brakeLeverImage, { duration: .6, rotation: 0 })
		})

		let resize = () => {
			bikeContainer.x = window.innerWidth - bikeContainer.width
			bikeContainer.y = window.innerHeight - bikeContainer.height
		}
		window.addEventListener('resize', resize)
		resize()

		// 实现骑行效果
		let particlesContainer = new PIXI.Container();
		this.stage.addChild(particlesContainer);

		particlesContainer.pivot.x = window.innerWidth / 2
		particlesContainer.pivot.y = window.innerHeight / 2
		particlesContainer.x = window.innerWidth / 2
		particlesContainer.y = window.innerHeight / 2

		particlesContainer.rotation = 38 * Math.PI / 180

		// 创建粒子
		let particles = []
		let colors = [0xf1cf54, 0xb5cea8, 0x333333] // 多颜色

		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 parItem = {
				sx: Math.random() * window.innerWidth,
				sy: Math.random() * window.innerHeight,
				gr: gr
			}

			gr.x = parItem.sx;
			gr.y = parItem.sy;

			particlesContainer.addChild(gr);
			particles.push(parItem)

		}

		function loop() {
			for (let i = 0; particles.length; i++) {
				let pItem = particles[i]
				pItem.gr.y += 20

				if (pItem.gr.y > window.innerHeight) {
					pItem.gr.y = 0
				}
			}
		}

		gsap.ticker.add(loop)


		// 一直移动,超出边界回到顶部,按住鼠标停止回弹效果,松开继续



	}
	createActionButton() {
		// 创建按钮图层
		const actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2


		// 使用gsap实现动画效果
		btnCircleImage.scale.x = btnCircleImage.scale.y = 0.8
		gsap.to(btnCircleImage.scale, { duration: 1, x: 1.2, y: 1.2, repeat: -1 })
		gsap.to(btnCircleImage.scale, { duration: 1, alpha: 0, repeat: -1 })
		return actionButton
	}
}

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

优化骑行动画

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {
		// 创建自行车图层
		const bikeContainer = new PIXI.Container();
		this.stage.addChild(bikeContainer);

		bikeContainer.scale.x = bikeContainer.scale.y = 0.2

		const brakeBikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture);
		const brakeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture);
		const brakeHandlerbarImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);

		bikeContainer.addChild(brakeBikeImage);
		bikeContainer.addChild(brakeLeverImage);
		bikeContainer.addChild(brakeHandlerbarImage);

		// 调整车闸的位置
		brakeLeverImage.pivot.x = 455
		brakeLeverImage.pivot.y = 455
		brakeLeverImage.x = 722
		brakeLeverImage.y = 900

		// 按钮图层
		let actionButton = this.createActionButton()
		actionButton.x = actionButton.y = 400;

		// 按钮与车闸的交互
		actionButton.interactive = true
		actionButton.buttonMode = true
		actionButton.on("mousedown", () => {
			// brakeLeverImage.rotation = Math.PI / 180 * -30
			gsap.to(brakeLeverImage, { duration: .8, rotation: Math.PI / 180 * -30 })
		})
		actionButton.on("mouseup", () => {
			gsap.to(brakeLeverImage, { duration: .6, rotation: 0 })
		})

		let resize = () => {
			bikeContainer.x = window.innerWidth - bikeContainer.width
			bikeContainer.y = window.innerHeight - bikeContainer.height
		}
		window.addEventListener('resize', resize)
		resize()

		// 实现骑行效果
		let particlesContainer = new PIXI.Container();
		this.stage.addChild(particlesContainer);

		particlesContainer.pivot.x = window.innerWidth / 2
		particlesContainer.pivot.y = window.innerHeight / 2
		particlesContainer.x = window.innerWidth / 2
		particlesContainer.y = window.innerHeight / 2

		particlesContainer.rotation = 38 * Math.PI / 180

		// 创建粒子
		let particles = []
		let colors = [0xf1cf54, 0xb5cea8] // 多颜色

		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 parItem = {
				sx: Math.random() * window.innerWidth,
				sy: Math.random() * window.innerHeight,
				gr: gr
			}

			gr.x = parItem.sx;
			gr.y = parItem.sy;

			particlesContainer.addChild(gr);
			particles.push(parItem)

		}

		let speed = 10
		function loop() {

			speed += .5
			speed = Math.min(speed, 30)

			for (let i = 0; particles.length; i++) {
				let pItem = particles[i]
				pItem.gr.y += speed
				pItem.gr.scale.y = 40
				pItem.gr.scale.x = .02

				if (pItem.gr.y > window.innerHeight) {
					pItem.gr.y = 0
				}
			}
		}

		function start() {
			speed = 0
			gsap.ticker.add(loop)
		}



		start()


		// 一直移动,超出边界回到顶部,按住鼠标停止回弹效果,松开继续



	}
	createActionButton() {
		// 创建按钮图层
		const actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2


		// 使用gsap实现动画效果
		btnCircleImage.scale.x = btnCircleImage.scale.y = 0.8
		gsap.to(btnCircleImage.scale, { duration: 1, x: 1.2, y: 1.2, repeat: -1 })
		gsap.to(btnCircleImage.scale, { duration: 1, alpha: 0, repeat: -1 })
		return actionButton
	}
}

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

添加刹车效果

最后将效果实现刹车效果

class BrakeBanner {
	constructor(selector) {
		// 初始化画布
		this.app = new PIXI.Application({
			width: window.innerWidth,
			height: window.innerHeight,
			backgroundColor: 0x0000ff,
			resizeTo: window
		})

		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.stage = this.app.stage

		this.loader.onComplete.add(() => {
			this.show()
		});
	}
	show() {
		// 创建自行车图层
		const bikeContainer = new PIXI.Container();
		this.stage.addChild(bikeContainer);

		bikeContainer.scale.x = bikeContainer.scale.y = 0.2

		const brakeBikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture);
		const brakeLeverImage = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture);
		const brakeHandlerbarImage = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture);

		bikeContainer.addChild(brakeBikeImage);
		bikeContainer.addChild(brakeLeverImage);
		bikeContainer.addChild(brakeHandlerbarImage);

		// 调整车闸的位置
		brakeLeverImage.pivot.x = 455
		brakeLeverImage.pivot.y = 455
		brakeLeverImage.x = 722
		brakeLeverImage.y = 900

		// 按钮图层
		let actionButton = this.createActionButton()
		actionButton.x = actionButton.y = 400;

		// 按钮与车闸的交互
		actionButton.interactive = true
		actionButton.buttonMode = true
		actionButton.on("mousedown", () => {
			// brakeLeverImage.rotation = Math.PI / 180 * -30
			gsap.to(brakeLeverImage, { duration: .8, rotation: Math.PI / 180 * -30 })
			pause()
		})
		actionButton.on("mouseup", () => {
			gsap.to(brakeLeverImage, { duration: .6, rotation: 0 })
			start()
		})

		let resize = () => {
			bikeContainer.x = window.innerWidth - bikeContainer.width
			bikeContainer.y = window.innerHeight - bikeContainer.height
		}
		window.addEventListener('resize', resize)
		resize()

		// 实现骑行效果
		let particlesContainer = new PIXI.Container();
		this.stage.addChild(particlesContainer);

		particlesContainer.pivot.x = window.innerWidth / 2
		particlesContainer.pivot.y = window.innerHeight / 2
		particlesContainer.x = window.innerWidth / 2
		particlesContainer.y = window.innerHeight / 2

		particlesContainer.rotation = 35 * Math.PI / 180

		// 创建粒子
		let particles = []
		let colors = [0xf1cf54, 0xb5cea8, 0xf1cf54, 0x818181, 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 parItem = {
				sx: Math.random() * window.innerWidth,
				sy: Math.random() * window.innerHeight,
				gr: gr
			}

			gr.x = parItem.sx;
			gr.y = parItem.sy;

			particlesContainer.addChild(gr);
			particles.push(parItem)

		}

		let speed = 0
		function loop() {

			speed += .5
			speed = Math.min(speed, 20)

			for (let i = 0; particles.length; i++) {
				let pItem = particles[i]
				pItem.gr.y += speed

				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; particles.length; i++) {
				let pItem = particles[i]
				pItem.gr.scale.y = 1
				pItem.gr.scale.x = 1

				gsap.to(pItem.gr, { duration: .6, x: pItem.sx, y: pItem.sy, ease: "elastic.out" })
			}
		}



		start()


		// 一直移动,超出边界回到顶部,按住鼠标停止回弹效果,松开继续



	}
	createActionButton() {
		// 创建按钮图层
		const actionButton = new PIXI.Container();
		this.stage.addChild(actionButton);
		let btnImage = new PIXI.Sprite(this.loader.resources['btn.png'].texture);
		let btnCircleImage = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);
		let btnCircleImage2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture);

		actionButton.addChild(btnImage);
		actionButton.addChild(btnCircleImage);
		actionButton.addChild(btnCircleImage2);

		// 更改按钮位置
		const btnImageWidth = btnImage.width
		btnImage.pivot.x = btnImageWidth / 2
		btnImage.pivot.y = btnImageWidth / 2
		const btnCircleImageWidth = btnCircleImage.width
		btnCircleImage.pivot.x = btnCircleImageWidth / 2
		btnCircleImage.pivot.y = btnCircleImageWidth / 2
		const btnCircleImageWidth2 = btnCircleImage2.width
		btnCircleImage2.pivot.x = btnCircleImageWidth2 / 2
		btnCircleImage2.pivot.y = btnCircleImageWidth2 / 2


		// 使用gsap实现动画效果
		btnCircleImage.scale.x = btnCircleImage.scale.y = 0.8
		gsap.to(btnCircleImage.scale, { duration: 1, x: 1.2, y: 1.2, repeat: -1 })
		gsap.to(btnCircleImage.scale, { duration: 1, alpha: 0, repeat: -1 })
		return actionButton
	}
}

通过PIXI+GSAP仿写vanmoof刹车动效 | 猿创营

最后

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

源码地址:https://github.com/wang-zhaofei/YCY-TrainingCamp-S1