开发H5项目,有时会遇到一个需求,需要制作抢红包,或者下红包雨的网页,这个实现步骤,如果拿现成的改来做是容易的,但是想着全靠自己做是不容易的,接下来开始讲,想不想自己做,有把握学到吗
首先创建一个网页文件,例如index.html
,制作下红包雨的页面,源代码如下,通过修改样式里设置好背景色,还有组件要填充到全屏,再加一个开始按钮(按钮可以不要,自动开始吧),写好大概逻辑,还有需要调用的一些方法
Red packet rain
接着,写一个加载脚本的处理逻辑,代码如下,使用RedPacketRain
对象创建前,需要先引用一个模块
const elemTimer = document.getElementById('timer');new RedPacketRain({id:'box',success:res=>{ res.onStart();let time=10;//这个是倒计时,单位slet timer = setInterval(()=>{elemTimer.innerText = `距离结束还有${time--}s`;if (time<0){clearInterval(timer);res.onStop({success:(res)=>{alert('游戏结束,\n钱袋有¥'+res.money)}});}},1000);}
},window);
接下来,看上面有引用的一个模块文件red_packet_rain.js
,没有的就把它新建好,在一个模块中去实现上面未实现的调用方法,代码如下
export default class RedPacketRain{constructor(conf,window){// 这里做一些初始化的工作...}
}
接下来,在初始化中去写方法的实现细节要复杂得多,如果看着比较吃力,就先收藏好,以后有时间慢慢摸索,边学边做
在构造方法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); }
}
绘制搞定了,接下来,就在重绘方法redraw()
中实现红包雨效果,代码如下
export default class RedPacketRain{//...constructor(conf,window){//...Object.assign(conf,{redPackW:50,//红包的宽度refreshDelay:60,//刷新延迟 msspeedDown:10,//下落速度waitTime:20,//60*60mswaitTimeRadom: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.wpurse.y && p.y+redPack.hpurse.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++;})};//...}
}
这样红包雨就能开始下了,还差个游戏互动,要实现移动钱袋子,玩家会在画布Canvas
元素上按下鼠标健,我们只需要在这里加上监听事件做处理即可,代码如下
export default class RedPacketRain{//...constructor(conf,window){//...//鼠标左键按下时监听事件做处理canvas.addEventListener('mousedown',(event)=>{const { x, y } = event;if (x>purse.x && xif (y>purse.y && ypurse.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=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;});//...}
}
讲到最后,用浏览器打开网页index.html
浏览看看,正常的话,运行效果图如下
💡小提示 试试修改传入的参数,例如
//...
new RedPacketRain({redPackW:50,//红包的宽度refreshDelay:60,//刷新延迟 msspeedDown:10,//下落速度waitTime:20,//60*60mswaitTimeRadom:5,//紧密度moneyRadom:0.05,//随机金额最大值success:(res)=>{//...}
});
说句实在话,画得红包和钱袋子看起来算不上美丽,献丑了,如果读者自己学会后,自己改就好了😄,
到此结束,如阅读中有遇到什么问题,请在文章结尾评论处留言,ヾ( ̄▽ ̄)ByeBye