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

如何实现SpeechSynthesisUtterance与唇动GIF的精准同步

问题解决:Web语音API唇动动画与语音停顿同步

问题说明

基于Web Speech API的项目,使用SpeechSynthesisUtterance朗读文本时,遇到句号、逗号、问号等标点会短暂停顿。需求是:

  • 朗读时显示唇动GIF
  • 标点停顿时显示静音GIF
  • 其余状态显示idle GIF
    目前语音播放过程中静音GIF的显示不准确,需要修复。

现有代码的核心问题

  1. 逻辑颠倒:onboundary事件中,判断到文本块无标点时才触发静音动画,完全和需求相反
  2. 硬编码停顿时长:固定200ms的静音时长,无法匹配语音实际停顿时间
  3. 边界事件监听不全:仅监听sentence和start类型,未覆盖punctuation等关键边界类型
  4. 状态管理混乱:多个函数重复处理GIF显示状态,容易出现冲突

修复后的完整代码

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文本转语音</title>
    <style>
        body {
            font-family: Arial, sans-serif;
        }

        .container {
            margin: 50px auto;
            width: 80%;
            text-align: center;
        }

        textarea {
            width: 100%;
            margin-bottom: 20px;
        }

        button {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
        }

        #lipGif,
        #lipSilent,
        #iddle {
            width: 34%;
            height: auto;
            display: none;
        }
        /* 初始显示idle状态 */
        #iddle {
            display: inline;
        }
    </style>
</head>

<body>
    <div class="container">
        <textarea id="textToSpeak" rows="4" placeholder="输入要朗读的文本..."></textarea>
        <button id="speakButton">开始朗读</button>
    </div>
    <img id="lipGif" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-_Tvyma0BGv056krKRI7c_LCjZyjzb68vMprSOqSziEK7Ut4a7QWU8qRit3qNau7gaHt_KMl9v1n4wGXvr6DxvnsWQeiLgLqieN_ghFIF6kDfmJvU2SRI6Kz_F_W1C3hDhuupiI0QwkPyHkwYm_9m7mO9ex9OLDb2z2aLgkD1GgaJf21MgelHpgZUN8I/s320/dux4.gif" alt="唇动动画">
    <img id="lipSilent" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivGwlopz4dCieqlQmrcbKP12KTTSb2_Pkji1zfhdRfE5afcKoQ5mC_tlMEoKq61qhoEolvJr-4b11fwZq0kAg3habVrfcKErtrEDSphWDNxedUZzaNPtDAYQ0ui_VEJ5Fsts5Gs9320EUDBTQ8VHTEec6AQKCDDkKU-w5Ooh1OItdOQABrlfVwbsj2sKA/s320/duxsilent.gif" alt="静音动画">
    <img id="iddle" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaME89fsOuRgrQsSRJTucinXsMCK26aA6_4PCtC0a0K3_nqvpwwKr_mI1NsMFEHSINgjY-divbArLYciXTacdUs-yD9csMsFjFGwkznNvHdtBUKHdt-ckykYV0tRp9572Wi2_JyKwbQii5pyZPuOiAVnJYzUjmbq3YSlrFmj_KKlIc0ilRdE1GUtqcT4I/s320/Dux_Male_Iddle.gif" alt="空闲动画">
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const textToSpeakInput = document.getElementById('textToSpeak');
            const speakButton = document.getElementById('speakButton');
            const lipGif = document.getElementById('lipGif');
            const lipSilent = document.getElementById('lipSilent');
            const iddle = document.getElementById('iddle');
            let pauseTimeoutId = null;

            speakButton.addEventListener('click', function() {
                const text = textToSpeakInput.value.trim();
                if (text !== '') {
                    // 清空之前的定时器,避免冲突
                    if (pauseTimeoutId) clearTimeout(pauseTimeoutId);
                    speakText(text);
                } else {
                    alert('请输入要朗读的文本');
                }
            });

            function speakText(text) {
                const utterance = new SpeechSynthesisUtterance(text);
                let lastCharIndex = 0;

                // 监听边界事件,处理标点停顿
                utterance.onboundary = function(event) {
                    // 清除之前的静音定时器
                    if (pauseTimeoutId) clearTimeout(pauseTimeoutId);

                    const textChunk = text.substring(lastCharIndex, event.charIndex + 1).trim();
                    lastCharIndex = event.charIndex + 1;

                    // 判断当前块是否以标点结尾,或者是标点边界
                    const isPunctuationEnd = /[.,!?]$/.test(textChunk) || event.name === 'punctuation';
                    if (isPunctuationEnd) {
                        // 显示静音动画
                        showSilentAnimation();
                        // 这里可以根据标点类型调整停顿时长,比如句号比逗号久
                        const pauseDuration = textChunk.endsWith('.') || textChunk.endsWith('?') || textChunk.endsWith('!') ? 800 : 300;
                        // 到点后恢复唇动动画
                        pauseTimeoutId = setTimeout(showLipAnimation, pauseDuration);
                    } else {
                        // 非标点边界,直接显示唇动动画
                        showLipAnimation();
                    }
                };

                utterance.onstart = function() {
                    showLipAnimation();
                };

                utterance.onend = function() {
                    // 朗读结束,回到idle状态
                    if (pauseTimeoutId) clearTimeout(pauseTimeoutId);
                    showIdleAnimation();
                };

                // 朗读被暂停时切换到静音
                utterance.onpause = function() {
                    showSilentAnimation();
                };

                // 朗读恢复时切换回唇动
                utterance.onresume = function() {
                    showLipAnimation();
                };

                speechSynthesis.speak(utterance);
            }

            function showLipAnimation() {
                lipGif.style.display = 'inline';
                lipSilent.style.display = 'none';
                iddle.style.display = 'none';
            }

            function showSilentAnimation() {
                lipSilent.style.display = 'inline';
                lipGif.style.display = 'none';
                iddle.style.display = 'none';
            }

            function showIdleAnimation() {
                iddle.style.display = 'inline';
                lipGif.style.display = 'none';
                lipSilent.style.display = 'none';
            }
        });
    </script>
</body>
</html>

关键修改说明

  1. 修正逻辑判断:当文本块以标点结尾或触发punctuation边界事件时,才显示静音动画
  2. 动态调整停顿时长:根据标点类型设置不同的静音时长(句号/感叹号/问号800ms,逗号300ms),更贴近真人朗读习惯
  3. 完善事件监听:新增onpause、onresume事件处理,适配语音暂停/恢复的状态切换
  4. 优化状态管理:用全局定时器ID管理超时任务,避免多个定时器冲突;拆分独立的状态切换函数,逻辑更清晰
  5. 初始状态优化:默认显示idle GIF,符合页面初始状态

内容的提问来源于stack exchange,提问作者monkey from Tanzania

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 15:07:15