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

news2025/1/17 13:51:02

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

目录一览

  • 1. 设计网页
  • 2. 编写脚本
  • 3. 编写模块
  • 4. 实现方法
    • 1. 绘制红包和钱袋子
    • 2. 下红包雨
    • 3. 移动钱袋子
  • 5.运行效果

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
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/10090.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式开发学习之--点亮LED灯(下)

上篇我们主要学习的是环境搭建和实际操作&#xff0c;这篇详细记录一下整个思考的过程。 首先&#xff0c;我们需要想一个问题&#xff0c;这个灯为什么会亮&#xff1f; 物理上来说&#xff0c;LED灯属于发光二极管&#xff0c;只要有正确的电压就会发亮。也就是说&#xff…

01 DevOps 之 Git 命令使用

1. 前言 由于项目没有外网&#xff0c;需要在内网打镜像。但自己对git 还不是太熟悉。看着pipline 一脸懵逼。所有针对git 命令在工作中常用的参数及用法简单学习记录下。因为git 是DevOps中的编码的一部分&#xff0c;下面先介绍下DevOps整体的框架。 1.1 DevOps介绍 DevOp…

Linux-磁盘分区,挂载

Linux分区 Linux来说无论有几个分区&#xff0c;分给哪一个目录使用&#xff0c;它归根结底就只有一个根目录&#xff0c;一个独立且唯一的文件结构&#xff0c;Linux中每个分区都是用来组成文件系统的一部分 Linux采用了一种载入的处理方法&#xff0c;它的整个文件系统包含了…

智能运维应用之道,告别企业数字化转型危机

面临的问题及挑战 数据中心发展历程 2000 年中国数据中心始建&#xff0c;至今已经历以下 3 大阶段。早期&#xff1a;离散型数据中心 IT 因以项目建设为导向&#xff0c;故缺乏规划且无专门运维管理体系&#xff0c;此外&#xff0c;开发建设完的项目均是独立运维维护&#…

rust编程初探-猜数游戏(chapter 2)

目录 1. 创建项目 2. 猜数的输入 3. 随机数生成 3.1 rand库依赖 3.2 随机数生成 4. 猜数和随机数的比对 4.1 std::cmp::Ordering类型 4.2 match表达式&#xff08;expression&#xff09; 4.3 输入类型的转换 5. 支持多次猜测&#xff08;使用循环&#xff09; 6. 错…

MySql学习之慢SQL优化和慢SQL案例

一、慢SQL优化思路 慢查询日志记录慢SQLexplain查询SQL的执行计划profile分析执行耗时Optimizer Trace分析详情 1、慢查询日志记录慢SQL show variables like slow_query_log%; show variables like long_query_time;查看下慢查询日志配置&#xff0c;我们可以使用show vari…

对MMVAE中IWAE代码实现的理解

原始的IWAE 优化目标&#xff1a; LIWAE(x1:M)Ez1:K∼qΦ(z∣x1:M)[log⁡∑k1K1KpΘ(zk,x1:M)qΦ(zk∣x1:M)]&#xff08;1&#xff09;\mathcal{L}_{\mathrm{IWAE}}\left(\boldsymbol{x}_{1: M}\right)\mathbb{E}_{\boldsymbol{z}^{1: K} \sim q_{\Phi}\left(\boldsymbol{z} …

JavaScript

目录 1、JavaScript简介 2、JavaScript引入方式 2.1、内部脚本 2.2、外部脚本 3、JavaScript基础语法 3.1、书写语法 3.2、输出语句 3.3、变量 3.4、数据类型 3.5、运算符 3.5.1、 和 的区别 3.5.2、类型转换 3.6、流程控制语句 3.6.1、if语句 3.6.3、for循环语…

【毕业设计】时间序列天气预测系统 - LSTM

文章目录0 前言1 数据集介绍2 开始分析2.1 单变量分析2.1.1 温度变量2.2 将特征和标签切片2.3 建模2.4 训练模型2.5 多变量分析2.5.1 压强、温度、密度随时间变化绘图2.5.2 将数据集转换为数组类型并标准化2.5.3 多变量建模训练训练3 最后0 前言 &#x1f525; Hi&#xff0c;…

vue项目身份认证,vuex,token

vuex存储用户登录信息以及解决页面刷新vuex数据丢失问题 我的文章&#xff1a;vuex页面刷新数据丢失问题的多种解决方法 有写到 身份认证 虽然完成了登录功能&#xff0c;但实际上现在用户没登录也能访问(对应的url),这样的话显得登录功能毫无意义。 为了让登录变得有意义&am…

【Linux】Linux系统管理详解

目录一.服务管理1.Linux中的进程和服务2.systemctl&#xff08;CentOS 7版本&#xff09;(1)基本语法(2)经验技巧二.系统运行级别1.CentOS的运行级别三.配置服务开机自启和关闭防火墙1.图形化服务开机自启2.命令行服务开机自启3.关闭防火墙自启动四.关机重启以下内容都是基于Ce…

排序算法-希尔排序

希尔排序 概念 希尔排序(Shell Sort)是插入排序的一种&#xff0c;它是针对直接插入排序算法的改进。 希尔排序又称缩小增量排序&#xff0c;因 DL.Shell 于 1959 年提出而得名。 它通过比较相距一定间隔的元素来进行&#xff0c;各趟比较所用的距离随着算法的进行而减小&a…

通达信最新交易接口系统开发源码有哪些?

通达信最新交易接口其实跟市面上的自动交易接口api是比较安全稳定接口&#xff0c;只需要通过第三方证券公司完成交易&#xff0c;也或者是个人与机构做私募量化投资也是可以的。但是最近小编就有注意到&#xff0c;在此之前的通达信接口已经完成了再次升级&#xff0c;那么&am…

JAVA开发(分布式SpringCloud全家桶一些组件读法)

配置管理&#xff0c;服务发现&#xff0c;断路器&#xff0c;路由&#xff0c;微代理&#xff0c;事件总线&#xff0c;全局锁&#xff0c;决策竞选&#xff0c;分布式会话构成SpringCloud的集合。 Eureka服务注册与发现&#xff08;Eureka&#xff1a;怎么读&#xff1f;&…

没基础的大学生如何自学c语言 ?

C语言具有高效、灵活、功能丰富、表达力强和较高的可移植性等特点&#xff0c;在程序设计中备受青睐。真的太多人学也有太多要学的东西了&#xff0c;以至于后台总有人问C语言该怎么学&#xff0c;甚至还有具体问编程问题的。 这一点专门针对「大部分时间都在写着重复的代码&a…

uniapp初步搭建:如何引入uview库(跨移动多端ui库)

uView是uni-app生态专用的UI框架&#xff0c;uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c; 可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台(引言自uni-app网) 1. 查看项目根目录有没有p…

公钥密码学中的公钥和私钥

公钥密码学解释&#xff1a;它是什么&#xff1f; 公钥基础设施 (PKI) 用于管理互联网通信中的身份和安全性。 启用 PKI 的核心技术是公钥密码术&#xff0c;这是一种依赖于使用两个相关密钥&#xff08;公钥和私钥&#xff09;的加密机制。 这两个密钥一起用于加密和解密消息。…

CM311-3_YST_晨星MSO9385_2+8_安卓9.0_TTL免费升级固件【含教程】

新魔百盒CM311-3_YST_晨星MSO9385_28_安卓9.0_TTL免费升级固件【含教程】 固件特点&#xff1a; 1、修改dns&#xff0c;三网通用&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、无开机广告&#xff0c;无系统更新&#xff0c;不在被强制升级&…

网站攻击技术,一篇打包带走!

大家好&#xff0c;今天给大家介绍一下&#xff0c;Web安全领域常见的一些安全问题。 1. SQL 注入 SQL注入攻击的核心在于让Web服务器执行攻击者期望的SQL语句&#xff0c;以便得到数据库中的感兴趣的数据或对数据库进行读取、修改、删除、插入等操作&#xff0c;达到其邪恶的…

分布式应用之监控平台zabbix的认识与搭建

内容预知 1.监控系统的相关知识 1.1 监控系统运用的原因 1.2 网站的可用性 1.3 市面上常用的监控系统 2.zabbix的相关知识 2.1 zabbix的概述 2.2 zabbix 是什么&#xff1f; 2.3 zabbix的监控原理 2.4 zabbix监控系统中五个常用程序 3. zabbix 服务端的部署 4. 部署…