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

这是一个抢红包雨游戏-使用HTML网页制作而成,用Canvas组件绘制的, 可用(手机)浏览器打开运行, 配套的教程请看相关文章https://blog.csdn.net/zs1028/article/details/127864697


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.οnlοad=()=>{
                //加载脚本的处理逻辑...
            }
        </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)=>{
         //...
    }
});
 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值