You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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();
});

关键实现说明

  1. 界面切换机制:通过给每个界面添加screen类,用active类控制显示/隐藏,所有界面都占满#memory-game容器,保证尺寸一致。
  2. 游戏核心逻辑:
    • 生成打乱的卡牌数组,创建卡牌元素
    • 控制每次最多翻转两张卡牌,检查匹配状态
    • 统计点击次数和倒计时,触发胜负判断
  3. 响应式设计:游戏容器使用min()函数适配不同屏幕尺寸,同时保持宽高比例稳定。

内容的提问来源于stack exchange,提问作者Gianloooca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 17:54:54