In this tutorial, I will teach you how to make a Memory Cards Game with Timer using HTML5, CSS3, and JavaScript. The complete source code of this JavaScript Memory Cards Game with a Time Limit functionality is given below.
You can download the full source code (including images) of this JavaScript Memory Cards Game with Timer at the end of this article.
Source Code of JavaScript Memory Cards Game with Timer
index.html
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>Memory Cards Game with Timer</title> <link rel="stylesheet" href="style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <div class="wrapper"> <ul class="cards"> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-1.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-2.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-3.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-4.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-5.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-6.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-5.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-6.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-1.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-2.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-3.png" alt="card-img"> </div> </li> <li class="card"> <div class="view front-view"> <img src="images/que_icon.svg" alt="icon"> </div> <div class="view back-view"> <img src="images/img-4.png" alt="card-img"> </div> </li> <div class="details"> <p class="time">Time: <span><b>20</b>s</span></p> <p class="flips">Flips: <span><b>0</b></span></p> <button>Refresh</button> </div> </ul> </div> <script src="script.js"></script> </body> </html>
script.js
const cards = document.querySelectorAll(".card"), timeTag = document.querySelector(".time b"), flipsTag = document.querySelector(".flips b"), refreshBtn = document.querySelector(".details button"); let maxTime = 20; let timeLeft = maxTime; let flips = 0; let matchedCard = 0; let disableDeck = false; let isPlaying = false; let cardOne, cardTwo, timer; function initTimer() { if(timeLeft <= 0) { return clearInterval(timer); } timeLeft--; timeTag.innerText = timeLeft; } function flipCard({target: clickedCard}) { if(!isPlaying) { isPlaying = true; timer = setInterval(initTimer, 1000); } if(clickedCard !== cardOne && !disableDeck && timeLeft > 0) { flips++; flipsTag.innerText = flips; clickedCard.classList.add("flip"); if(!cardOne) { return cardOne = clickedCard; } cardTwo = clickedCard; disableDeck = true; let cardOneImg = cardOne.querySelector(".back-view img").src, cardTwoImg = cardTwo.querySelector(".back-view img").src; matchCards(cardOneImg, cardTwoImg); } } function matchCards(img1, img2) { if(img1 === img2) { matchedCard++; if(matchedCard == 6 && timeLeft > 0) { return clearInterval(timer); } cardOne.removeEventListener("click", flipCard); cardTwo.removeEventListener("click", flipCard); cardOne = cardTwo = ""; return disableDeck = false; } setTimeout(() => { cardOne.classList.add("shake"); cardTwo.classList.add("shake"); }, 400); setTimeout(() => { cardOne.classList.remove("shake", "flip"); cardTwo.classList.remove("shake", "flip"); cardOne = cardTwo = ""; disableDeck = false; }, 1200); } function shuffleCard() { timeLeft = maxTime; flips = matchedCard = 0; cardOne = cardTwo = ""; clearInterval(timer); timeTag.innerText = timeLeft; flipsTag.innerText = flips; disableDeck = isPlaying = false; let arr = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]; arr.sort(() => Math.random() > 0.5 ? 1 : -1); cards.forEach((card, index) => { card.classList.remove("flip"); let imgTag = card.querySelector(".back-view img"); setTimeout(() => { imgTag.src = `images/img-${arr[index]}.png`; }, 500); card.addEventListener("click", flipCard); }); } shuffleCard(); refreshBtn.addEventListener("click", shuffleCard); cards.forEach(card => { card.addEventListener("click", flipCard); });
style.css
/* Import Google Font - Poppins */ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); *{ margin: 0; padding: 0; box-sizing: border-box; font-family: 'Poppins', sans-serif; } p{ font-size: 20px; } body{ display: flex; align-items: center; justify-content: center; min-height: 100vh; background: #6563ff; } ::selection{ color: #fff; background: #6563ff; } .wrapper{ padding: 25px; background: #f8f8f8; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } .cards, .card, .view, .details, p{ display: flex; align-items: center; justify-content: center; } .cards{ height: 350px; width: 350px; flex-wrap: wrap; justify-content: space-between; } .cards .card{ cursor: pointer; position: relative; perspective: 1000px; transform-style: preserve-3d; height: calc(100% / 4 - 10px); width: calc(100% / 4 - 10px); } .card.shake{ animation: shake 0.35s ease-in-out; } @keyframes shake { 0%, 100%{ transform: translateX(0); } 20%{ transform: translateX(-13px); } 40%{ transform: translateX(13px); } 60%{ transform: translateX(-8px); } 80%{ transform: translateX(8px); } } .cards .card .view{ width: 100%; height: 100%; user-select: none; pointer-events: none; position: absolute; background: #fff; border-radius: 7px; backface-visibility: hidden; transition: transform 0.25s linear; box-shadow: 0 3px 10px rgba(0,0,0,0.1); } .card .front-view img{ max-width: 17px; } .card .back-view{ transform: rotateY(-180deg); } .card .back-view img{ max-width: 40px; } .card.flip .front-view{ transform: rotateY(180deg); } .card.flip .back-view{ transform: rotateY(0); } .details{ width: 100%; margin-top: 15px; padding: 0 20px; border-radius: 7px; background: #fff; height: calc(100% / 4 - 30px); justify-content: space-between; box-shadow: 0 3px 10px rgba(0,0,0,0.1); } .details p{ font-size: 18px; height: 17px; padding-right: 18px; border-right: 1px solid #ccc; } .details p span{ margin-left: 8px; } .details p b{ font-weight: 500; } .details button{ cursor: pointer; font-size: 14px; color: #6563ff; border-radius: 4px; padding: 4px 11px; background: #fff; border: 2px solid #6563ff; transition: 0.3s ease; } .details button:hover{ color: #fff; background: #6563ff; } @media screen and (max-width: 700px) { .cards{ height: 350px; width: 350px; } .card .front-view img{ max-width: 16px; } .card .back-view img{ max-width: 40px; } } @media screen and (max-width: 530px) { .cards{ height: 300px; width: 300px; } .card .back-view img{ max-width: 35px; } .details{ margin-top: 10px; padding: 0 15px; height: calc(100% / 4 - 20px); } .details p{ height: 15px; font-size: 17px; padding-right: 13px; } .details button{ font-size: 13px; padding: 5px 10px; border: none; color: #fff; background: #6563ff; } }