Home > Enterprise >  How can I make an event listener fire only once?
How can I make an event listener fire only once?

Time:11-06

I am building a very simple memory matching game with 6 cards. Each card is in a div with the class of .card that contains two more div elements - front face of the card (just a simple green background img) and a back face of the card (a random image i've picked).

Backfaces enter image description here

Frontfaces enter image description here

Every card has a click event listener. Once you click the card, the card rotates from it's back side to it's front side and reveals the image. If the two images you've picked are a match, you receive 1 Score. If the images you've picked do not happen to be a match, all of the cards rotate to their back face and you receive 1 to "Fails" and you lose the game.

The main problem is that I can click and rotate the same card over and over again due to the event listener. Is there any way to make it fire only once? I've tried to use once: true on the event listener but that caused the cards to be unclickable even after losing the game thus i've removed it.

const cards = document.querySelectorAll(".card");
const images = document.querySelectorAll("img");
let scoreElement = document.getElementById("score");
let failsElement = document.getElementById("fails");
let failsAmount = 0;
let scoreAmount = 0;
let pickedCards = [];

function pickCards() {
    // Iterate through every div with the class of ".card"
    for (let i = 0; i < cards.length; i  ) {
        // Add an event listener to them
        cards[i].addEventListener("click", function() {
            // Rotate the card
            cards[i].classList.toggle("flipcard");
            // After clicking on the card that contains the img, push the img into the pickedCards array.
            pickedCards.push(images[i]);
            checkForMatch();
        });
    }
}

function checkForMatch() {
    // Check the source of the images from the array
    if (pickedCards[0].src == pickedCards[1].src) {
        alert("Match!  1 score");
        scoreAmount  ;
        scoreElement.textContent = "Score: "   scoreAmount;
        // Empty the array so the player can pick another 2 cards
        pickedCards = [];
    }

    else {
        hideCards();
        alert("Not a match! Sorry!");
        failsAmount  ;
        failsElement.textContent = "Fails: "   failsAmount;
        pickedCards = [];
    }
}

function hideCards() {
    for (let i = 0; i < cards.length; i  ) {
        setTimeout(function() {
            // Remove the flipcard class from the cards and show the front face again.
            cards[i].classList.remove("flipcard");
        }, 1000); 
    }
}

pickCards();
* {
    padding: 0;
    margin: 0;
}

*, *:before, *:after {
    box-sizing: inherit;
}

html {
    box-sizing: border-box;
}

html,body {
    height: 100%;
}

.card-container {
    display: flex;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
    perspective: 1200;
}

.flipcard {
    transform: rotateY(-180deg);
}

.card {
    width: 200px;
    height: 200px;
    transition: all 0.7s;
    transform-style: preserve-3d;
    margin: 0em 1em;
}

.front-card, .back-card {
    width: 200px;
    height: 200px;
    position: absolute;
    top: 0;
    left: 0;
    backface-visibility: hidden;
}

.front-card {
    background: url("./IMGs/block.jpg") no-repeat center/cover;
}

.back-card {
    transform: rotateY(-180deg);
}

.back-card img {
    width: 200px;
    height: 200px;
}

#score, #fails {
    font-size: 2em;
}
<div class="card-container">
        <div class="card">
            <div class="front-card"></div>
            <div id="card1" class="back-card"><img src="IMGs/eric.jpg" alt=""></div>
        </div>
        <div class="card">
            <div class="front-card"></div>
            <div id="card2" class="back-card"><img src="IMGs/john.webp" alt=""></div>
        </div>
        <div class="card">
            <div class="front-card"></div>
            <div id="card3" class="back-card"><img src="IMGs/salim.png" alt=""></div>
        </div>
        <div class="card">
            <div class="front-card"></div>
            <div id="card4" class="back-card"><img src="IMGs/salim.png" alt=""></div>
        </div>
        <div class="card">
            <div class="front-card"></div>
            <div id="card5" class="back-card"><img src="IMGs/eric.jpg" alt=""></div>
        </div>
        <div class="card">
            <div class="front-card"></div>
            <div id="card6" class="back-card"><img src="IMGs/john.webp" alt=""></div>
        </div>
    </div>
    <p id="score">Score: 0</p>
    <p id="fails">Fails: 0</p>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

I am still new to this, so any tips will be aprecitated, thank you.

CodePudding user response:

For your case just add an if-statement before flipping and checking for match:

cards[i].addEventListener('click', function () {
  if (!cards[i].classList.contains('flipcard')) {
     cards[i].classList.toggle('flipcard')
     pickedCards.push(images[i])
     checkForMatch()
  }
})

CodePudding user response:

If you delegate you can test much better than turning off event handlers

document.querySelector(".card-container").addEventListener("click", function(e) {
  const tgt = e.target.closest("div");
  if (tgt && tgt.classList.contains("card")) {
    if (tgt.classList.contains("clicked")) return;
    tgt.classList.add("clicked");
    .... // rest of handling here
  }
})

CodePudding user response:

This is easy to solve.

  1. Create an Array or variables to check if the respective card was clicked. Example:

    cardsClicked = [false, false, false, false, false, false];

  2. On the click event, modify the selected cardsClicked[n] to true. AND, check (IF) the selected card already was selected.

That's it.

  • Related