JS记忆游戏替换图片后卡牌无法自动关闭问题求助
JavaScript记忆游戏修改后逻辑异常排查
问题现象
将原游戏中的emoji替换为图片后,图片可正常显示,但出现两个核心异常:
- 选中两张不同卡牌时,卡牌不会自动关闭
- 可连续点击多张卡牌,不受“一次最多翻两张”的限制
问题原因及修复方案
1. 点击事件目标识别错误
原代码的点击判断逻辑仅能识别直接点击.card元素的场景,但实际点击区域是卡牌的子元素(.card-front、.card-back或内部<img>),导致:
- 错误地将子元素传入
flipCard函数,给子元素添加flipped类而非.card元素 - 状态计数
state.flippedCards混乱,无法正确限制同时翻开的卡牌数量
修复代码:
使用closest()方法定位到真正的.card元素,同时增加对已翻开/已匹配卡牌的点击限制:
const attachEventListeners = () => { document.addEventListener('click', event => { const card = event.target.closest('.card'); const eventTarget = event.target; if (card && !card.classList.contains('flipped') && !card.classList.contains('matched')) { flipCard(card); } else if (eventTarget.nodeName === 'BUTTON' && !eventTarget.className.includes('disabled')) { startGame(); } }) }
2. 卡牌匹配判断逻辑错误
原游戏通过.card元素的innerText判断匹配,但替换为图片后,.card的innerText为空,导致匹配判断完全失效:不同卡牌的空字符串对比会返回true,错误标记为匹配;或因无有效对比值导致逻辑混乱。
修复代码:
改为通过卡牌内部<img>的src属性判断匹配:
const flipCard = card => { state.flippedCards++ state.totalFlips++ if (!state.gameStarted) { startGame() } if (state.flippedCards <= 2) { card.classList.add('flipped') } if (state.flippedCards === 2) { const flippedCards = document.querySelectorAll('.flipped:not(.matched)') // 提取两张卡牌的图片地址进行对比 const src1 = flippedCards[0].querySelector('.card-back img').src; const src2 = flippedCards[1].querySelector('.card-back img').src; if (src1 === src2) { flippedCards[0].classList.add('matched') flippedCards[1].classList.add('matched') } setTimeout(() => { flipBackCards() }, 1000) } // 修正胜利判断:所有卡牌匹配完成才触发胜利 if (!document.querySelectorAll('.card:not(.matched)').length) { setTimeout(() => { selectors.boardContainer.classList.add('flipped') selectors.win.innerHTML = ` <span class="win-text"> 你赢了!<br /> 共 <span class="highlight">${state.totalFlips}</span> 步<br /> 用时 <span class="highlight">${state.totalTime}</span> 秒 </span> ` clearInterval(state.loop) }, 1000) } }
完整修复后的JavaScript代码
const selectors = { boardContainer: document.querySelector('.board-container'), board: document.querySelector('.board'), moves: document.querySelector('.moves'), timer: document.querySelector('.timer'), start: document.querySelector('button'), win: document.querySelector('.win') } const state = { gameStarted: false, flippedCards: 0, totalFlips: 0, totalTime: 0, loop: null } const shuffle = array => { const clonedArray = [...array] for (let index = clonedArray.length - 1; index > 0; index--) { const randomIndex = Math.floor(Math.random() * (index + 1)) const original = clonedArray[index] clonedArray[index] = clonedArray[randomIndex] clonedArray[randomIndex] = original } return clonedArray } const pickRandom = (array, items) => { const clonedArray = [...array] const randomPicks = [] for (let index = 0; index < items; index++) { const randomIndex = Math.floor(Math.random() * clonedArray.length) randomPicks.push(clonedArray[randomIndex]) clonedArray.splice(randomIndex, 1) } return randomPicks } const generateGame = () => { const dimensions = selectors.board.getAttribute('data-dimension') if (dimensions % 2 !== 0) { throw new Error("The dimension of the board must be an even number.") } const emojis = ['https://i.imgur.com/GLS9S5f.jpg', 'https://i.imgur.com/IN9C2qz.jpg', 'https://i.imgur.com/Ke2ubzv.jpg', 'https://i.imgur.com/PbvJDyR.jpg', 'https://i.imgur.com/L3ysai2.jpg', 'https://i.imgur.com/1NxzhTV.jpg', 'https://i.imgur.com/aksV9O3.jpg', 'https://i.imgur.com/gYsZdE4.jpg', 'https://i.imgur.com/LXo6iW3.jpg', 'https://i.imgur.com/wYrEwNR.jpg'] const picks = pickRandom(emojis, (dimensions * dimensions) / 2) const items = shuffle([...picks, ...picks]) const cards = ` <div class="board" style="grid-template-columns: repeat(${dimensions}, auto)"> ${items.map(item => ` <div class="card"> <div class="card-front"></div> <div class="card-back"><img src="${item}"></div> </div> `).join('')} </div> ` const parser = new DOMParser().parseFromString(cards, 'text/html') selectors.board.replaceWith(parser.querySelector('.board')) } const startGame = () => { state.gameStarted = true selectors.start.classList.add('disabled') state.loop = setInterval(() => { state.totalTime++ selectors.moves.innerText = `${state.totalFlips} moves` selectors.timer.innerText = `time: ${state.totalTime} sec` }, 1000) } const flipBackCards = () => { document.querySelectorAll('.card:not(.matched)').forEach(card => { card.classList.remove('flipped') }) state.flippedCards = 0 } const flipCard = card => { state.flippedCards++ state.totalFlips++ if (!state.gameStarted) { startGame() } if (state.flippedCards <= 2) { card.classList.add('flipped') } if (state.flippedCards === 2) { const flippedCards = document.querySelectorAll('.flipped:not(.matched)') const src1 = flippedCards[0].querySelector('.card-back img').src; const src2 = flippedCards[1].querySelector('.card-back img').src; if (src1 === src2) { flippedCards[0].classList.add('matched') flippedCards[1].classList.add('matched') } setTimeout(() => { flipBackCards() }, 1000) } if (!document.querySelectorAll('.card:not(.matched)').length) { setTimeout(() => { selectors.boardContainer.classList.add('flipped') selectors.win.innerHTML = ` <span class="win-text"> 你赢了!<br /> 共 <span class="highlight">${state.totalFlips}</span> 步<br /> 用时 <span class="highlight">${state.totalTime}</span> 秒 </span> ` clearInterval(state.loop) }, 1000) } } const attachEventListeners = () => { document.addEventListener('click', event => { const card = event.target.closest('.card'); const eventTarget = event.target; if (card && !card.classList.contains('flipped') && !card.classList.contains('matched')) { flipCard(card); } else if (eventTarget.nodeName === 'BUTTON' && !eventTarget.className.includes('disabled')) { startGame(); } }) } generateGame() attachEventListeners()
内容的提问来源于stack exchange,提问作者davy jones
相关产品推荐
相关产品推荐

