Native JS, in accordance with the idea of object-oriented practice of an H5 small game; The project does not rely on other plug-ins, only in the implementation of the function, did not do too much style;

If that’s ok, hit Star: gitHub address

Start by simply writing the page and styling it

<body> <div class="wrap"> <div id="redPacketWarp"></div> </div> <div id="startGameBtn">startGame</div> <div Id ="showTime" </div> </body>Copy the code
.wrap {
    position: relative;
    background: rgba(47, 59, 106, 0.3);
    position: absolute;
    left: 20px;
    right: 20px;
    top: 20px;
    bottom: 100px;
    font-size: 0;
    overflow: hidden;
}
#redPacketWarp {
    width: 100%;
    height: 100%;
    font-size: 0;
}
#startGameBtn {
    position: absolute;
    bottom: 20px;
    left: 20px;
    text-align: center;
    background: red;
    height: 50px;
    line-height: 50px;
    width: 60%;
    color: #fff;
    font-size: 20px;
    border-radius: 30px;
    box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.5);
}
Copy the code

Create a new GameRedpacket class as a framework for the entire game;

First we define some properties of this class and a method to start the game startGame();

class GameRedpacket {
    constructor({el, redImgUrl, redpacketNum, gameTime, gameScore, gameTimesOut}){
        this.redPacketWarp = document.getElementById(el);
        if(! el || !this.redPacketWarp) return false; // There is no frame to return directly
        this.score = 0; / / score
        this.redImgUrl = redImgUrl; // The red envelope
        this.callback_gameScore = gameScore; // The score callback returns the score
        this.callback_gameTimesOut = gameTimesOut; // The timeout callback returns the score
        this.gameTime = gameTime || 30000; // Game time
        this.redpacketNum = redpacketNum || 10; // The number of red packets
        this.redPacketWarp.width = this.redPacketWarp.offsetWidth;
        this.redPacketWarp.height = this.redPacketWarp.offsetHeight;
        this.isOver = false; // flag whether the game is over
    }
    startGame () {
        // todo}}Copy the code

The parameter that GameRedpacket must pass in is EL (element ID of the game frame) redImgUrl has a default image in the project, and can also pass in a custom image;

New A GameRedpacket instance object

window.onload = (a)= > {
    let $startGameBtn = document.getElementById('startGameBtn');
    let $showTime = document.getElementById('showTime');
    let gameRedpacket = new GameRedpacket({
      el: 'redPacketWarp'.timeout: 40000.gameScore: (score) = > {
        $showTime.innerHTML = ` score:${score}`;
      },
      gameTimesOut: (score) = > {
        console.log(score)
        alert('Time is up and your final score is :'+score); }}); $startGameBtn.onclick =function(){ gameRedpacket.startGame(); }}Copy the code

The time is 40s, the score is changed in showTime, if the game is over, the final score will pop up; GameScore and gameTimesOut are two callback functions, score and gameTimesOut respectively, which both return the current score;

Now back to GameRedpacket, let’s start writing methods for the GameRedpacket class

class GameRedpacket {
    / /...
    // Start the game
    startGame () {
        this.init();
        this.addRedpacketList();
        this.render();
    }
    init() {
        this.score = 0;
        this.isOver = false;
        this.redPacketWarp.innerHTML = ' ';
        this.redPacketList = [];
    }
    addRedpacketList() {
        // todo
    }
    render() {
        // todo}}Copy the code

StartGame () is the entry point of the game, init() is called to initialize, addRedpacketList() adds red packets, and reder() renders; For extensibility and flexibility, we break it down by function

Add red packet addRedpacketList()

class GameRedpacket {
    / /...
    addRedpacketList () {
        let timeout = setInterval((a)= > {
            if (this.redPacketList.length <= this.redpacketNum) {
                this.redPacketList.push(new RedPacket(this));
            } else{ clearInterval(timeout); }},1000)
    }
    render () {}
}
class RedPacket {
    constructor (gameRedpacket) {
        this.gameRedpacket = gameRedpacket;
        this.redImgUrl = gameRedpacket.redImgUrl;
        this.width = 50;// Here we set the default width and height
        this.height = 50;
        this.hasInit = false; // Whether it has been initialized
        this.id = new Date().getTime().toString(); // use the timestamp as the id
        this.$img = document.createElement('IMG'); // Red packets will be rendered with img elements
        this.$img.src = this.redImgUrl;
        this.$img.setAttribute('data-id'.this.id); // Save the id to data-id for future use
        this.hasSelected = false; // Mark whether the red envelope has been selected
        this.y = 0; // The y attribute is used to mark the drop distance of the red envelope
        this.speed = Math.random()*2 + 3; //speed is the speed of the red envelope}}Copy the code

All addRedpacketList() does is add red packets; The idea here is to create a new redpacket object every 1s, until the redpacket object is equal to the set number of red packets, stop adding red packets. These red packets can be recycled. When the red packets fall to the bottom of the screen, they can be returned to the top, so that a steady stream of red packets can be realized. In order to be clearer and easier to expand, RedPacket will be a new class RedPacket; At the same time, we will pass in GameRedpacket as a parameter, so that RedPacket can also get the attributes of GameRedpacket.

Next comes the render() function to render the red envelope,

Here we mainly use the requestAnimationFrame, implementing a recursive, need to understand requestAnimationFrame, please stamp window. RequestAnimationFrame (the callback)

class GameRedpacket {
    / /...
    render () {
        if (this.isOver) return false;
        this.renderRedpacket();
        this.requestAnimationFrame = requestAnimationFrame(this.render.bind(this));
    }
    renderRedpacket () {
        this.redPacketList.forEach(redPacket= > {
            // If the red envelope is not initialized, you can initialize it first and add the red envelope element to the game box
            if(! redPacket.hasInit) { redPacket.init(); redPacket.hasInit =true;
            }
            // If the fall height exceeds the screen, reinitialize
            if (redPacket.y > this.redPacketWarp.height + redPacket.height*2) {
                redPacket.initStart();
            }
            redPacket.move();// The red envelope moves}}})class RedPacket {
    / /...
    init () {
        this.initStart ();
        this.gameRedpacket.redPacketWarp.appendChild(this.$img);
    }
    initStart () {
        this.hasSelected = false;
        this.y = 0;
        this.speed = Math.random()*2 + 3;
        this.x = (this.gameRedpacket.redPacketWarp.width - this.width) * Math.random();
        this.$img.setAttribute('style'.`
            position: absolute;
            left: The ${this.x}px;
            top: -The ${this.height}px;
            width: The ${this.width}px;
            height: The ${this.height}px;
        `)
    }
    move () {
        if (!this.hasSelected) {
            this.y += this.speed;
            this.$img.style.transform = `translateY(The ${this.y}px)`; }}}Copy the code

Red envelope drop using translateY to achieve. Each animation changes the y attribute of the red envelope, speed is a random range of numbers, so that the red envelope is not a speed falling;

Do here, click to start the game, our red envelope has been able to move, of course, if you can not click, what is the meaning of this game, then we do the red envelope click

Click on the red envelopes

The click event of the red envelope is delegated to the outermost element of the game, the el passed in;

class GameRedpacket { //... AddEvent () {/ / add event this. RedPacketWarp. AddEventListener (' touchstart '(e) = > {the if (this. IsOver) return; // Let id = e.target.getattribute ('data-id'); // Let $RedPacket = this.getredPacketobjById (id); if ($RedPacket) { this.addScore(); $redpacket.selected (); }})} getRedPacketObjById (id) { Const INDEX = this.redPacketList.map(o => {return O.ID}).indexof (id); return INDEX < 0 ? false : this.redPacketList[INDEX]; } addScore() { this.score ++; // score +1 this.callback_gamescore && this.callback_gamescore (this.score); } } class RedPacket { //... selected () { this.hasSelected = true; this.$img.setAttribute('style', ` transition: all 300ms; position: absolute; left: 50%; top: 50%; margin-left: -${this.width/2}px; margin-top: -${this.height/2}px; width: ${this.width}px; z-index: 10; height: ${this.height}px; The transform: scale (1.5) translateY (0); `); setTimeout(() => { if (this.gameRedpacket.isOver) return; this.initStart(); }, 300); } move() { if (! this.hasSelected) { this.y += this.speed; this.$img.style.transform = `translateY(${this.y}px)`; }}}Copy the code

Each score will trigger the score callback we passed in and return the current score score. The way the red envelope is selected is very simple. Here we need to change the hasSelected object to true so that the red envelope will not continue to move. After the red envelope is selected, there is a 300ms cutscene. After the animation, initStart() is restarted to restore the initial state. If the game is over, no processing is done.

The callback function

Time to play: gameTimesOut will pop up the current score

class GameRedpacket {
    / /...
    startGame () {
        this.timeout_gameTime = setTimeout((a)= > {
            this.gameTimesOut();
        }, this.gameTime);
    }
    gameTimesOut () {
        this.isOver = true;
        this.callback_gameTimesOut && this.callback_gameTimesOut(this.score);
        this.init(); }}Copy the code

At the beginning of the game, we set a timer, the time is our set time this.gameTime; When the time is up, the gameTimesOut function is fired and isOver is set to true; I’m going to do a callback, I’m going to pass in score and I’m going to do init() and I’m going to restore everything;

And the score return function we’ve already done in addScore

class GameRedpacket {
    / /...
    addScore() {
        this.score ++; / / + 1
        this.callback_gameScore && this.callback_gameScore(this.score); }}Copy the code

There are a lot of points that can be optimized, requestAnimationFramede compatibility issues, image preloading, etc., later have time to supplement. With the mentality of sharing and learning more, welcome to exchange 】

Project address: github.com/Dranein/act…

lijinyuan

[email protected]