【JavaScript】制作一个抢红包雨页面

开发H5项目,有时会遇到一个需求,需要制作抢红包,或者下红包雨的网页,这个实现步骤,如果拿现成的改来做是容易的,但是想着全靠自己做是不容易的,接下来开始讲,想不想自己做,有把握学到吗

1. 设计网页


首先创建一个网页文件,例如index.html,制作下红包雨的页面,源代码如下,通过修改样式<style>里设置好背景色,还有组件要填充到全屏,再加一个开始按钮(按钮可以不要,自动开始吧),写好大概逻辑,还有需要调用的一些方法

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>Red packet rain</title>
		<style>
			html{
				height: 100%;
			}
			body{
				margin: 0;
				height: 100%;
			}
			#box{
				width: 100%;
				height: 100%;
				background: linear-gradient(#f00,#fff);
			}
			.float-box{
				position: absolute;
				left: 0;
				top: 0;
				right: 0;
				color: #fff;
				text-align: center;
				padding: 20px;
			}
		</style>
	</head>
	<body>
		<div class="float-box">
			<span id="timer"></span>
		</div>
		<div id="box"></div>
		<script type="module">
			import RedPacketRain from './red_packet_rain.js';
			
			window.onload=()=>{
				//加载脚本的处理逻辑...
			}
		</script>
	</body>
</html>

2. 编写脚本

接着,写一个加载脚本的处理逻辑,代码如下,使用RedPacketRain对象创建前,需要先引用一个模块

const elemTimer = document.getElementById('timer');

new RedPacketRain({
	id:'box',
	success:res=>{						
		res.onStart();
		let time=10;//这个是倒计时,单位s
		let timer = setInterval(()=>{
			elemTimer.innerText = `距离结束还有${time--}s`;
			if (time<0){
				clearInterval(timer);
				res.onStop({
					success:(res)=>{
						alert('游戏结束,\n钱袋有¥'+res.money)
					}
				});
			}
		},1000);
	}
},window);

3. 编写模块

接下来,看上面有引用的一个模块文件red_packet_rain.js,没有的就把它新建好,在一个模块中去实现上面未实现的调用方法,代码如下

export default class RedPacketRain{
	
	constructor(conf,window){
		// 这里做一些初始化的工作...
				
	}
}

4. 实现方法

接下来,在初始化中去写方法的实现细节要复杂得多,如果看着比较吃力,就先收藏好,以后有时间慢慢摸索,边学边做

1. 绘制红包和钱袋子

在构造方法constructor()里做初始化,可以先把需要的东西,就是红包和钱袋子两个,都绘制出来,代码如下

export default class RedPacketRain{
	
	// 定义需要用到的一些私有属性
	#canvasCtx;
	#imgBg;
	#drawImgPurse;
	#drawImgRedPacket;
	#isContinue=false;
	
	constructor(conf,window){
		// 这里做一些初始化的工作...
		const { document } = context;
		if(conf.id==undefined) throw new Error('not find element id');
		Object.assign(conf,{
			redPackW:50,//红包的宽度
			//...
		});
		let box = document.getElementById(conf.id);
		let canvas = document.createElement('canvas');
		canvas.width = box.offsetWidth;
		canvas.height = Math.max(box.offsetHeight,canvas.width);
		const ctx = canvas.getContext('2d');
		ctx.textAlign = 'center';
		ctx.font = ctx.font.replace(/\d+/,23);
		//红包数据
		const redPack = {
			w: conf.redPackW,
			h: conf.redPackW*1.4,
			absX: 10,
			absY: 10
		};
		//钱袋子数据
		const purse = {
			w: conf.redPackW*2,
			h: conf.redPackW*2,
			x: 0,
			y: 0,
			absX: 100,
			absY: 10,
			count: 0,
			money: 0
		};
		//清空画布方法
		const drawClearEmpty = ()=>{
			ctx.clearRect(0,0,canvas.width,canvas.height);
		};
		//绘制红包方法
		const drawRedPacket = (x,y)=>{
			//绘制红包形状
			let r = redPack.w*0.1;
			ctx.fillStyle='#f00';
			ctx.strokeStyle='#333';
			ctx.beginPath();
			ctx.arc(x+r,y+r,r,Math.PI,-0.5*Math.PI);
			let x2 = x+redPack.w;
			ctx.lineTo(x2-r,y);
			ctx.arc(x2-r,y+r,r,-0.5*Math.PI,0);
			let y2 = y+redPack.h;
			ctx.lineTo(x2,y2-r);
			ctx.arc(x2-r,y2-r,r,0,0.5*Math.PI);
			ctx.lineTo(x+r,y2);
			ctx.arc(x+r,y2-r,r,0.5*Math.PI,Math.PI);
			ctx.closePath();
			ctx.fill();
			//绘制盖子
			let centerX = x+redPack.w/2;
			let centerY = y-redPack.w*0.6;
			ctx.save();
			ctx.clip();
			ctx.arc(centerX,centerY,redPack.w,-0.5*Math.PI,1.5*Math.PI);
			ctx.stroke();
			ctx.restore();
			//绘制盖子纽扣
			centerY = y+redPack.w*0.4;
			ctx.fillStyle='#991';
			ctx.beginPath();
			ctx.arc(centerX,centerY,redPack.w*0.15,0,2*Math.PI);
			ctx.fill();
			ctx.stroke();
			//绘制纽扣中间
			r = redPack.w*0.05;
			ctx.fillStyle='#000';
			ctx.beginPath();
			ctx.rect(centerX-r,centerY-r,2*r,2*r);
			ctx.fill();
		};
		//绘制钱袋子方法
		const drawPurse = (x,y)=>{
			let r = purse.w/2;
			let centerX = x + r;
			let centerY = y + purse.h-conf.redPackW*0.8;
			//绘制袋子容器形状
			ctx.fillStyle='#f55';
			ctx.beginPath();
			ctx.arc(centerX,centerY,r,0,2*Math.PI);
			ctx.fill();
			ctx.stroke();
			//绘制袋口
			ctx.beginPath();
			ctx.save();
			ctx.translate(-centerX*0.6,0);
			ctx.scale(1.6,1);
			ctx.arc(centerX,centerY-r*0.62,r*0.6,0,2*Math.PI);
			ctx.fill();
			ctx.stroke();
			ctx.beginPath();
			ctx.fillStyle='#333';
			ctx.arc(centerX,centerY-r*0.62,r*0.5,0,2*Math.PI);
			ctx.fill();
			ctx.stroke();
			ctx.restore();
			purse.h = purse.h+conf.redPackW*0.2;
		};
		//绘制袋子图片方法
		this.#drawImgPurse = (isSaved)=>{
			if(!isSaved) drawClearEmpty();
			ctx.drawImage(this.#imgBg,purse.absX,purse.absY,purse.w,purse.h,purse.x,purse.y,purse.w,purse.h);
			ctx.fillStyle='#ff5';
			let r = purse.w/2;
			ctx.fillText('¥'+purse.money.toFixed(2),purse.x+r,purse.y+(purse.h+r)*0.55,purse.w);
		};
		//绘制红包图片方法
		this.#drawImgRedPacket = (x,y)=>{
			ctx.drawImage(this.#imgBg,redPack.absX,redPack.absY,redPack.w,redPack.h,x,y,redPack.w,redPack.h);
		};
		
		//其它逻辑省略...
		
		//重绘方法
		const redraw = () => {
			//...
		};
		
		//封装上下文,开始和结束方法通过初始化完成后返回给外部调用
		const Context = {
			onStart:()=>{
				if(this.#isContinue) return;
				this.#isContinue=true;
				redraw();//开始时,调用重绘方法
			},
			onStop:(conf={})=>{
				if(!this.#isContinue) return;
				this.#isContinue=false;
				//停止后,将钱袋子的数据传给外部调用者
				if(typeof conf.success=='function') conf.success({
					count: purse.count,
					money: purse.money
				})
			}
		};
		//加载图片...
		(new Promise((resolve,reject)=>{
			drawRedPacket(redPack.absX,redPack.absY);
			drawPurse(purse.absX,purse.absY);
			let img = new Image();
			img.onload = ()=>resolve(img);
			img.onerror = reject;
			img.src = canvas.toDataURL();
		})).then(res=>{
			this.#imgBg=res;//绘制好后生成图片
			purse.x=(canvas.width-purse.w)/2;
			purse.y=canvas.height-purse.h;
			//接下来我们会以这个图片作为素材来绘制动画
			this.#drawImgPurse();
			if(typeof conf.success=='function') conf.success(Context);//传给外部
		}).catch(err=>{
			throw new Error(err)
		});
		this.#canvasCtx = ctx;
		box.appendChild(canvas);	
	}
}

2. 下红包雨

绘制搞定了,接下来,就在重绘方法redraw()中实现红包雨效果,代码如下

export default class RedPacketRain{
	
	//...
	
	constructor(conf,window){
		//...
		Object.assign(conf,{
			redPackW:50,//红包的宽度
			refreshDelay:60,//刷新延迟 ms
			speedDown:10,//下落速度
			waitTime:20,//60*60ms
			waitTimeRadom:5,//紧密度
			moneyRadom:0.05,//随机金额最大值
		});
		//...
		
		//生成随机位置的红包
		const createRedPacket = () => {
			let padding = 10;
			return {
				x: padding + Math.trunc(Math.random()*(canvas.width-redPack.w-padding)),
				y: 0-redPack.h,
				money: 0.01 + Math.trunc(Math.random()*100*conf.moneyRadom)/100,
			}
		};
		//存放所有红包数据的集合
		const redPackets = [];
		redPackets.push(createRedPacket());
		let i=0;
		//重绘方法
		const redraw = () => {
			if(!this.#isContinue) return;
			//考虑到性能,建议每次调用
			window.requestAnimationFrame(()=>{
				this.#drawImgPurse();
				let outIndex=[];
				let x = purse.x+purse.w;
				let y = purse.y+purse.h;
				redPackets.forEach((p,index)=>{
					this.#drawImgRedPacket(p.x,p.y);
					p.y+=conf.speedDown;
					//判断红包是否在钱袋子上面,收红包处理一下
					if(p.x>purse.x && p.x+redPack.w<x && p.y>purse.y && p.y+redPack.h<y) {
						purse.count++;
						purse.money+=p.money;//将收到的红包金额加到钱袋子中
						outIndex.push(index);
					} else if(p.y>canvas.height) {
						outIndex.push(index);//将不再出现的红包加入标记
					}
				});
				outIndex.forEach(i=>redPackets.splice(i,1));//删除被标记的红包
				setTimeout(redraw,conf.refreshDelay);
				if (i>conf.waitTime){
					if (conf.waitTimeRadom>0) {
						if (Math.random()*conf.waitTimeRadom>conf.waitTimeRadom/2) {
							i=0;
							i++;
							return;
						}
					}
					redPackets.push(createRedPacket());//再生成随机红包
					i=0;
				}
				i++;
			})
		};
		//...
	}
}

3. 移动钱袋子

这样红包雨就能开始下了,还差个游戏互动,要实现移动钱袋子,玩家会在画布Canvas元素上按下鼠标健,我们只需要在这里加上监听事件做处理即可,代码如下

export default class RedPacketRain{
	
	//...
	
	constructor(conf,window){
		//...
		
		//鼠标左键按下时监听事件做处理
		canvas.addEventListener('mousedown',(event)=>{
			const { x, y } = event;
			if (x>purse.x && x<purse.x+purse.w) {
				if (y>purse.y && y<purse.y+purse.h) {
					purse.touch={ x:x-purse.x, y:y-purse.y };
				}
			}
		});
		//鼠标左键按下并移动时监听事件做处理
		canvas.addEventListener('mousemove',(event)=>{
			if(!purse.touch) return;
			const { x, y } = event;
			if (!(y>purse.y && y<purse.y+purse.h)) return;
			else if (x<purse.touch.x) return;
			else if (x>=canvas.width-(purse.w-purse.touch.x)) return;
			purse.x = x-purse.w/2;//如果是合法的左右移动,就改变钱袋子的坐标
		});
		//鼠标左键松开时监听事件做处理
		canvas.addEventListener('mouseup',(event)=>{
			if(!purse.touch) return;
			purse.touch=null;
		});
		
		//...
	}
}

5.运行效果

讲到最后,用浏览器打开网页index.html浏览看看,正常的话,运行效果图如下
请添加图片描述

💡小提示 试试修改传入的参数,例如

//...
new RedPacketRain({
	redPackW:50,//红包的宽度
	refreshDelay:60,//刷新延迟 ms
	speedDown:10,//下落速度
	waitTime:20,//60*60ms
	waitTimeRadom:5,//紧密度
	moneyRadom:0.05,//随机金额最大值
	success:(res)=>{
 		//...
	}
});

说句实在话,画得红包和钱袋子看起来算不上美丽,献丑了,如果读者自己学会后,自己改就好了😄,
到此结束,如阅读中有遇到什么问题,请在文章结尾评论处留言,ヾ( ̄▽ ̄)ByeBye
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TA远方

谢谢!收到你的爱╮(╯▽╰)╭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值