使用Vanilla JavaScript开发Simon游戏时音频顺序播放异常求助
Simon游戏音频序列播放问题修复
问题描述
开发Simon游戏初期,已成功生成随机颜色数组,但playSequentially函数无法按预期依次播放对应音频:重复出现的音频(如示例中的red)常无法再次播放,行为不可预测。例如数组['purple', 'red', 'blue', 'red', 'purple', 'red']中,purple可播放两次,但red仅播放一次。尝试过重置currentTime、修改ended属性等方法未解决。
问题代码
JavaScript代码
let greenBtn = document.getElementById("green-button"); let redBtn = document.getElementById("red-button"); let blueBtn = document.getElementById("blue-button"); let purpleBtn = document.getElementById("purple-button"); let greenAudio = document.getElementById("green-audio"); let redAudio = document.getElementById("red-audio"); let blueAudio = document.getElementById("blue-audio"); let purpleAudio = document.getElementById("purple-audio"); let playback = document.getElementById("play"); greenBtn.addEventListener("click", () => { greenAudio.play(); }) redBtn.addEventListener("click", () => { redAudio.play(); }) blueBtn.addEventListener("click", () => { blueAudio.play(); }) purpleBtn.addEventListener("click", () => { purpleAudio.play(); }) let audioArray = [greenAudio, blueAudio, redAudio, purpleAudio]; function playAudio(audio) { audio.play(); } function getAudioFromColor(color) { switch (color) { case "green": return greenAudio; case "blue": return blueAudio; case "red": return redAudio; case "purple": return purpleAudio; } } //create random array of colors of length n function getRandomColorArray(n) { let randomColorArray = [] let colors = ["green", "blue", "red", "purple"]; let i = 0; while (i < n) { let index = Math.floor(Math.random() * 4); console.log(index) randomColorArray.push(colors[index]); i++; } return randomColorArray; } function playSequentially(audioColorArray) { let delay = 500; let currentAudio = getAudioFromColor(audioColorArray[0]); currentAudio.play(); for (let i = 0; i < audioColorArray.length - 1; i++) { delay += 500; currentAudio = getAudioFromColor(audioColorArray[i]); currentAudio.onended = setTimeout(() => { getAudioFromColor(audioColorArray[i + 1]).play(); }, delay); } } playback.addEventListener("click", () => { // let randomColorArray = getRandomColorArray(5); let randomColorArray = ['purple', 'red', 'blue', 'red', 'purple', 'red'] console.log(randomColorArray) playSequentially(randomColorArray); });
CSS代码
div { margin: 10px; /* padding: 10px; */ } .game-btn { border: 1px solid black; border-radius: 20%; width: 200px; height: 200px; } .green { background-color: green; } .blue { background-color: blue; } .red { background-color: red } .purple { background-color: purple; } .green:hover { background-color: lightgreen; } .blue:hover { background-color: lightblue; } .red:hover { background-color: lightcoral } .purple:hover { background-color: plum; } #play { width: 100px; background-color: pink; }
HTML代码
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <title>Document</title> <div class="container" style="max-width: 75%;"> <div class="row justify-content-center"> <div class=" btn" id="play"> Playback</div> </div> <div class="row justify-content-center"> <div class="game-btn green btn" id="green-button"></div> <audio id="green-audio" src="./sounds/539252-Kalimba-INTIMATE-D5-01.wav"></audio> <div class="game-btn blue btn" id="blue-button"></div> <audio id="blue-audio" src="./sounds/539256-Kalimba-INTIMATE-E6-01.wav"></audio> </div> <div class="row justify-content-center"> <div class="game-btn red btn" id="red-button"></div> <audio id="red-audio" src="./sounds/539257-Kalimba-INTIMATE-F4-01.wav"></audio> <div class="game-btn purple btn" id="purple-button"></div> <audio id="purple-audio" src="./sounds/539258-Kalimba-INTIMATE-F5-01.wav"></audio> </div> </div> <script src="./assignment22.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
问题根源
playSequentially函数的实现逻辑存在两个核心问题:
- 重复覆盖
onended事件:当同一音频元素(如redAudio)多次出现时,后续循环会覆盖之前设置的onended回调,导致之前的回调无法触发。 - 错误的延迟逻辑:使用递增的固定延迟与
onended事件结合,会导致时序混乱——音频播放时长可能不等于固定的500ms,延迟叠加后会让后续播放时机完全错位。
修复方案
改用递归方式处理序列播放,每次等待当前音频播放结束后再启动下一个,同时确保播放前重置音频状态:
function playSequentially(audioColorArray) { // 递归播放函数 function playNext(index) { if (index >= audioColorArray.length) return; // 序列播放完成 const audio = getAudioFromColor(audioColorArray[index]); // 重置音频状态:回到起点,暂停之前的播放(如果有的话) audio.currentTime = 0; audio.pause(); // 播放当前音频,结束后触发下一个 audio.play().then(() => { audio.onended = () => { playNext(index + 1); }; }).catch(err => { console.error("播放失败:", err); playNext(index + 1); // 即使播放失败,继续下一个 }); } playNext(0); // 从第一个元素开始播放 }
额外优化建议
- 为按钮点击事件也添加音频重置逻辑,避免重复点击时音频无法播放:
greenBtn.addEventListener("click", () => { greenAudio.currentTime = 0; greenAudio.play(); }) // 其他按钮同理
- 添加播放状态锁,防止多次点击Playback按钮导致多个序列同时播放:
let isPlaying = false; playback.addEventListener("click", () => { if (isPlaying) return; isPlaying = true; let randomColorArray = ['purple', 'red', 'blue', 'red', 'purple', 'red']; console.log(randomColorArray); playSequentially(randomColorArray, () => { isPlaying = false; // 播放完成后解锁 }); }); // 修改playSequentially函数,接收完成回调 function playSequentially(audioColorArray, onComplete) { function playNext(index) { if (index >= audioColorArray.length) { onComplete?.(); return; } const audio = getAudioFromColor(audioColorArray[index]); audio.currentTime = 0; audio.pause(); audio.play().then(() => { audio.onended = () => { playNext(index + 1); }; }).catch(err => { console.error("播放失败:", err); playNext(index + 1); }); } playNext(0); }
内容的提问来源于stack exchange,提问作者Laura Mansfield
相关产品推荐
相关产品推荐

