Simon游戏灯光闪烁:for循环变量更新快于setInterval中CSS变化的问题
解决Simon游戏中灯光闪烁与音效同步的问题
这个问题我之前做类似记忆类游戏时也碰到过——同步的for循环跑得太快,完全不等闪烁动画和音效完成,导致所有音效一下子全触发,根本对应不上灯光的节奏。核心原因就是同步循环不会等待异步操作(比如setTimeout、音频播放)完成,得用异步控制流来调整时序才行。
第一步:把闪烁函数改成返回Promise
首先得让灯光闪烁的操作变成可等待的异步任务,把原来的闪烁逻辑包装成返回Promise的函数,这样我们就能精准知道闪烁什么时候完成:
// 改造后的闪烁函数,返回Promise function flashLight(colorElement) { // 提前保存原始颜色,方便后续恢复 if (!colorElement.originalColor) { colorElement.originalColor = colorElement.style.backgroundColor; } return new Promise((resolve) => { // 切换到高亮颜色 colorElement.style.backgroundColor = colors[1]; // 假设colors[1]是高亮状态的颜色 // 等待闪烁时长后恢复原色,标记任务完成 setTimeout(() => { colorElement.style.backgroundColor = colorElement.originalColor; resolve(); }, 500); // 500ms是闪烁持续时间,可根据需求调整 }); }
第二步:用async/await重构序列播放函数
接下来把播放序列的函数改成异步函数,用for...of循环配合await,让每一次闪烁和音效都等上一个步骤完成后再执行:
// 异步版序列播放函数 async function playSequence(moves) { for (const move of moves) { // 等待当前灯光闪烁完成 await flashLight(move.color); // 检查是否是目标高亮颜色,播放音效 if (move.color.style.backgroundColor === colors[0]) { // 如果音效播放也是异步的,建议也改成返回Promise的形式 await playSound(); } // 可选:在闪烁之间加一段停顿,让节奏更清晰 await new Promise(resolve => setTimeout(resolve, 200)); } }
(可选)让音效播放也支持等待
如果你的音效播放没做异步处理,也可以把它改成返回Promise的函数,避免音效重叠:
function playSound() { return new Promise((resolve) => { const audio = new Audio('your-highlight-sound.mp3'); // 音频播放结束后标记任务完成 audio.addEventListener('ended', resolve); audio.play().catch(err => console.error('音效播放失败:', err)); }); }
为什么这样能解决问题?
await关键字会暂停当前异步函数的执行,直到后面的Promise完成。这样for...of循环就变成了“串行”执行:每一次闪烁完成后,才会检查是否播放音效,等音效(如果有的话)播放完,再等待停顿,然后才处理下一个灯光闪烁。完全不会出现循环跑完了,动画和音效还没完成的情况。
替代方案:递归处理序列
如果你不想用async/await,也可以用递归来逐个处理每个move,本质是利用Promise的链式调用:
function playSequence(moves, currentIndex = 0) { // 递归终止条件:所有move处理完毕 if (currentIndex >= moves.length) return; const move = moves[currentIndex]; flashLight(move.color) .then(() => { // 检查是否播放音效 if (move.color.style.backgroundColor === colors[0]) { return playSound(); } return Promise.resolve(); }) .then(() => { // 等待停顿后处理下一个move setTimeout(() => { playSequence(moves, currentIndex + 1); }, 200); }); }
两种方法都能解决问题,个人更推荐async/await的写法,代码更直观易读。
内容的提问来源于stack exchange,提问作者icewizard
相关产品推荐
相关产品推荐

