如何用Vanilla JavaScript在div内实现无弹窗记忆卡牌游戏内容切换?
记忆卡牌游戏实现方案(无弹窗、同尺寸界面切换)
需求概述
我有一个承载简易记忆卡牌游戏的div,初始显示两个按钮:「游戏说明」和「开始游戏」。要求仅用原生JavaScript实现所有操作在该div内完成,不使用弹窗:
- 点击「游戏说明」按钮后,按钮消失,div内显示说明内容和「返回菜单」按钮
- 点击「开始新游戏」按钮后,div内启动游戏
- 初始菜单区域与游戏区域保持相同尺寸(响应式变化除外)
完整实现代码
HTML 结构
<body> <!-- 音频资源 --> <audio id="win-rick" class="hide" controls> <source src="assets/audio/you-winnn.mp3" type="audio/mpeg" /> Your browser does not support the audio element. </audio> <audio id="lose-rick" class="hide" controls> <source src="assets/audio/game-overrr.mp3" type="audio/mpeg" /> Your browser does not support the audio element. </audio> <!-- 主标题 --> <h1 class="heading">Memento Amiga</h1> <!-- 游戏容器(核心区域,所有界面都在这里切换) --> <div id="memory-game"> <!-- 初始菜单界面 --> <div id="menu-screen" class="screen active"> <button id="instrBtn" type="button">游戏说明</button> <button id="startBtn" type="button">开始新游戏</button> </div> <!-- 游戏说明界面 --> <div id="instructions-screen" class="screen"> <h3>游戏规则</h3> <ul> <li>点击卡牌翻转,找到两两相同的卡牌</li> <li>在倒计时结束前完成所有配对即可获胜</li> <li>点击次数会被统计,尽量减少点击哦</li> </ul> <button id="backToMenuBtn" type="button">返回菜单</button> </div> <!-- 游戏界面 --> <div id="game-screen" class="screen"> <div id="card-container"></div> </div> </div> <!-- 统计区域 --> <div id="click-numbers"> <p id="click-counter-p">点击次数: <span id="click-counter-n">0</span></p> <p id="timer-id">倒计时: <span id="real-time">60</span></p> </div> <script src="assets/js/script.js"></script> </body>
CSS 样式(确保容器尺寸统一)
/* 基础样式重置 */ * { margin: 0; padding: 0; box-sizing: border-box; } .heading { text-align: center; margin: 2rem 0; } /* 核心游戏容器:固定最小尺寸,响应式缩放 */ #memory-game { width: min(90vw, 600px); height: min(80vh, 500px); margin: 0 auto; border: 2px solid #333; border-radius: 8px; padding: 1rem; position: relative; overflow: hidden; } /* 所有界面统一占满容器 */ .screen { width: 100%; height: 100%; position: absolute; top: 0; left: 0; display: none; flex-direction: column; justify-content: center; align-items: center; gap: 1.5rem; padding: 2rem; background: #fff; } /* 激活的界面显示 */ .screen.active { display: flex; } /* 菜单按钮样式 */ #menu-screen button { padding: 1rem 2rem; font-size: 1.1rem; cursor: pointer; border: none; border-radius: 4px; background: #4CAF50; color: white; } #menu-screen button:hover { background: #45a049; } /* 说明界面样式 */ #instructions-screen ul { list-style-position: inside; line-height: 1.6; } #backToMenuBtn { padding: 0.8rem 1.5rem; cursor: pointer; border: none; border-radius: 4px; background: #2196F3; color: white; } /* 卡牌容器样式 */ #card-container { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.8rem; width: 100%; height: 100%; } .card { background: #ddd; border-radius: 8px; cursor: pointer; display: flex; justify-content: center; align-items: center; font-size: 2rem; transform: rotateY(180deg); transition: transform 0.3s ease; } .card.flipped { transform: rotateY(0); background: #fff; } .card.matched { background: #8BC34A; cursor: default; } /* 统计区域样式 */ #click-numbers { display: flex; justify-content: center; gap: 2rem; margin-top: 1.5rem; font-size: 1.1rem; } .hide { display: none; }
JavaScript 逻辑(界面切换 + 核心游戏功能)
// 获取DOM元素 const memoryGame = document.getElementById('memory-game'); const menuScreen = document.getElementById('menu-screen'); const instructionsScreen = document.getElementById('instructions-screen'); const gameScreen = document.getElementById('game-screen'); const instrBtn = document.getElementById('instrBtn'); const startBtn = document.getElementById('startBtn'); const backToMenuBtn = document.getElementById('backToMenuBtn'); const cardContainer = document.getElementById('card-container'); const clickCounter = document.getElementById('click-counter-n'); const timerDisplay = document.getElementById('real-time'); // 界面切换函数 function showScreen(screenElement) { // 隐藏所有界面 document.querySelectorAll('.screen').forEach(screen => { screen.classList.remove('active'); }); // 显示目标界面 screenElement.classList.add('active'); } // 菜单按钮事件 instrBtn.addEventListener('click', () => { showScreen(instructionsScreen); }); backToMenuBtn.addEventListener('click', () => { showScreen(menuScreen); // 返回菜单时重置统计 clickCounter.textContent = '0'; timerDisplay.textContent = '60'; clearInterval(timerInterval); }); // 游戏核心逻辑 let cards = []; let flippedCards = []; let matchedPairs = 0; let clickCount = 0; let timerInterval; let timeLeft = 60; // 生成卡牌数组 function generateCards() { // 这里用字母作为示例,可替换为图片或其他内容 const cardValues = ['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D', 'E', 'E', 'F', 'F', 'G', 'G', 'H', 'H']; // 打乱数组 cards = cardValues.sort(() => Math.random() - 0.5); } // 创建卡牌元素 function createCards() { cardContainer.innerHTML = ''; cards.forEach((value, index) => { const card = document.createElement('div'); card.classList.add('card'); card.dataset.value = value; card.textContent = value; card.addEventListener('click', flipCard); cardContainer.appendChild(card); }); } // 翻转卡牌逻辑 function flipCard(e) { const card = e.target; // 跳过已匹配或已翻转的卡牌 if (card.classList.contains('flipped') || card.classList.contains('matched')) return; // 最多同时翻转两张 if (flippedCards.length < 2) { card.classList.add('flipped'); flippedCards.push(card); clickCount++; clickCounter.textContent = clickCount; // 两张都翻转后检查匹配 if (flippedCards.length === 2) { checkMatch(); } } } // 检查卡牌匹配 function checkMatch() { const [card1, card2] = flippedCards; if (card1.dataset.value === card2.dataset.value) { // 匹配成功 card1.classList.add('matched'); card2.classList.add('matched'); matchedPairs++; flippedCards = []; // 检查是否全部匹配 if (matchedPairs === cards.length / 2) { clearInterval(timerInterval); document.getElementById('win-rick').play(); setTimeout(() => { alert('恭喜获胜!'); showScreen(menuScreen); }, 500); } } else { // 匹配失败,翻转回去 setTimeout(() => { card1.classList.remove('flipped'); card2.classList.remove('flipped'); flippedCards = []; }, 1000); } } // 倒计时函数 function startTimer() { timeLeft = 60; timerDisplay.textContent = timeLeft; timerInterval = setInterval(() => { timeLeft--; timerDisplay.textContent = timeLeft; if (timeLeft === 0) { clearInterval(timerInterval); document.getElementById('lose-rick').play(); setTimeout(() => { alert('时间到,游戏结束!'); showScreen(menuScreen); }, 500); } }, 1000); } // 开始游戏按钮事件 startBtn.addEventListener('click', () => { showScreen(gameScreen); // 重置游戏状态 matchedPairs = 0; clickCount = 0; flippedCards = []; clickCounter.textContent = '0'; clearInterval(timerInterval); // 生成并创建卡牌 generateCards(); createCards(); // 启动倒计时 startTimer(); });
关键实现说明
- 界面切换机制:通过给每个界面添加
screen类,用active类控制显示/隐藏,所有界面都占满#memory-game容器,保证尺寸一致。 - 游戏核心逻辑:
- 生成打乱的卡牌数组,创建卡牌元素
- 控制每次最多翻转两张卡牌,检查匹配状态
- 统计点击次数和倒计时,触发胜负判断
- 响应式设计:游戏容器使用
min()函数适配不同屏幕尺寸,同时保持宽高比例稳定。
内容的提问来源于stack exchange,提问作者Gianloooca
相关产品推荐
相关产品推荐

