JS Promise首次迭代Resolve失效:SVG动画与TTS配音同步异常
SVG路径动画与TTS配音同步异常修复
问题现象
实现SVG路径动画与微软TTS配音同步播放时,首次迭代中若语音先于动画结束,会提前进入下一轮播放;但动画先结束时功能正常。
原代码
async function playWithSound(pathArrangement, i, len) { const synth = window.speechSynthesis; pathid = pathArrangement[i]; script = scriptData[pathData[pathid].name]; console.log(script) const utterance = new SpeechSynthesisUtterance(script); utterance.voice = neerjaVoice || synth.getVoices()[0]; await synth.speak(utterance); utterance.onstart = () => { document.getElementById(pathid).style.animationPlayState = 'running'; followPencil(pathid); } // Create a promise that resolves when the speech synthesis ends const speechEndPromise = new Promise((resolve) => { utterance.onend = resolve; }); const animationEndPromise = new Promise((resolve) => { document.getElementById(pathid).addEventListener('animationend', () => { resolve(); }, { once: true }); }); // wait for promises to resolve and call the next stack await Promise.all([animationEndPromise, speechEndPromise]); console.log("resolved both") i++; if (i < len) { playWithSound(pathArrangement, i, len); } return; }
问题根源
- 无意义的
await:synth.speak()不是异步函数,await无法等待语音播放,导致事件绑定逻辑可能在语音开始/结束后才执行,时序错乱。 - 全局变量污染:
pathid和script未用let/const声明,属于全局变量,递归调用时会被后续迭代覆盖,引发不可预料的元素绑定错误。 - 事件绑定顺序错误:
utterance.onstart绑定在speak()之后,极端情况下语音已启动但事件监听未生效,导致动画未正确触发,或后续Promise逻辑异常。
修复后的代码
async function playWithSound(pathArrangement, i, len) { const synth = window.speechSynthesis; // 声明局部变量,避免全局污染 const pathid = pathArrangement[i]; const script = scriptData[pathData[pathid].name]; console.log(script); const utterance = new SpeechSynthesisUtterance(script); utterance.voice = neerjaVoice || synth.getVoices()[0]; const pathElement = document.getElementById(pathid); // 确保动画初始为暂停状态(如果CSS没设置的话) pathElement.style.animationPlayState = 'paused'; // 先绑定所有事件,再启动语音 const speechEndPromise = new Promise(resolve => { utterance.onend = resolve; }); const animationEndPromise = new Promise(resolve => { pathElement.addEventListener('animationend', () => { resolve(); }, { once: true }); }); utterance.onstart = () => { pathElement.style.animationPlayState = 'running'; followPencil(pathid); }; // 启动语音,无需await synth.speak(utterance); // 等待语音和动画都结束 await Promise.all([animationEndPromise, speechEndPromise]); console.log("resolved both"); i++; if (i < len) { playWithSound(pathArrangement, i, len); } }
额外注意事项
- 确保SVG元素的动画CSS中设置了
animation-play-state: paused作为初始状态,避免动画提前运行。 - 若
neerjaVoice是异步加载的,需确保在调用playWithSound前已获取到该语音,否则会 fallback 到默认语音。
内容的提问来源于stack exchange,提问作者AwaisMehnga
相关产品推荐
相关产品推荐

