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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 05:48:22