ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 JS 复刻微信语音播放动画:TaoToken 配置与调试骨架

用 JS 复刻微信语音播放动画:TaoToken 配置与调试骨架 1. 微信语音气泡动画到底难在哪做 IM 前端的人大概率都碰过这个需求聊天列表里那条绿色或米黄色的语音条点一下要开始「波纹跳动」再点一下要停播放结束还得自动复位。看起来只是换个图真写起来坑不少。微信语音播放动画的本质是用 JS 定时器按固定节奏切换一组状态图或 CSS 类让静态气泡产生「声波在动」的错觉。它不需要 canvas也不需要 Web Audio 的频谱分析核心就是setInterval 类名替换 播放时长兜底。适合谁看正在做 H5 聊天页、客服工单系统、在线问诊对话流的同学或者你手上已经有一个能播 AMR/MP3 的插件比如 BenzAMRRecorder.js但动画和播放状态总是对不齐。这篇会给你一套能直接跑的骨架CSS 关键帧参数、JS 状态机、以及用 TaoToken 统一 Key/API 通道做本地调试的settings.json与config.toml配置。为什么调试阶段要扯到 API 通道因为语音气泡的时长、左右朝向、音频地址这些元数据真实项目里往往来自后端接口本地 mock 和线上联调如果 Key 管理混乱动画还没调完就先被 401 卡住了。我试过的做法是把「动画层」和「数据层」彻底分开。动画层只认三个东西——当前播放的 DOM 节点、总时长、方向类名数据层通过一个统一的 API 通道拿语音元信息。这样你换播放器插件、换后端动画代码几乎不用动。2. TaoToken 前置把调试用的 Key 和通道先理清本地调动画时最烦的是接口地址和 Key 散落在各个文件里。TaoToken 在这里的角色是一个统一的模型/API 通道你可以把它理解成「一个入口 一把 Key」本地前端请求语音元数据、或者调模型生成测试文案都走同一个 base URL。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 这个不加 UTM直接填进配置。你需要先拿到 Key进控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 创建一个。创建后复制那串sk-开头的字符串只显示一次丢了就重建。注意Key 不要写进前端仓库的明文文件里。本地调试可以用.env.local或独立的config.toml并确保它进了.gitignore。如果你只是想先验证通道通不通、模型能不能回话可以直接用模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 发一条消息试试不用写代码就能确认 Key 有效。长期做编码和 Agent 类任务的话Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 会更省心额度模型和调用方式都在里面说明。接入细节和报错码对照看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。3. 可复制配置settings.json 与 config.toml 骨架下面两份配置是给本地调试用的。settings.json放在前端项目根目录供 Vite/Node 脚本读取config.toml放在你习惯的配置目录供命令行工具或后端 mock 服务读取。两者字段含义一致只是格式不同方便你在不同工具链里复用同一把 Key 和同一个 base URL。3.1 settings.json{ taotoken: { baseUrl: https://taotoken.net/api, apiKey: sk-替换成你自己的Key, timeoutMs: 15000, retry: 2 }, voice: { bubbleIntervalMs: 300, frameCount: 3, defaultDurationMs: 6000, leftClass: pngOnLeft, rightClass: pngOnRight } }bubbleIntervalMs就是波纹切换的节奏300ms 是微信观感比较接近的值frameCount是状态图数量常见是 3 帧循环defaultDurationMs是拿不到真实时长时的兜底避免动画永远停不下来。3.2 config.toml[taotoken] base_url https://taotoken.net/api api_key sk-替换成你自己的Key timeout_ms 15000 retry 2 [voice] bubble_interval_ms 300 frame_count 3 default_duration_ms 6000 left_class pngOnLeft right_class pngOnRight提示apiKey和api_key都建议通过环境变量注入比如TAOTOKEN_API_KEY配置文件里只留占位符。这样你分享 demo 给别人时不会泄露。3.3 动画关键帧参数与 CSS 骨架如果你不想用 PNG 序列纯 CSS 也能做波纹。核心是让三条竖线的scaleY错峰变化.voice-bubble { display: inline-flex; align-items: center; gap: 3px; height: 28px; padding: 0 12px; border-radius: 20px; cursor: pointer; background-color: #64d74a; } .voice-bubble.left { background-color: #f6f3d5; } .voice-bubble .bar { width: 3px; height: 8px; border-radius: 2px; background: #fff; transform-origin: center; } .voice-bubble.playing .bar { animation: wave 0.9s infinite ease-in-out; } .voice-bubble.playing .bar:nth-child(2) { animation-delay: 0.15s; } .voice-bubble.playing .bar:nth-child(3) { animation-delay: 0.3s; } keyframes wave { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(2.4); } }0.9s总周期、0.15s错峰、scaleY从 1 到 2.4这三个参数调完基本就是微信那个味道。如果你坚持用 PNG 序列把.playing换成定时器切类名即可节奏同样用bubbleIntervalMs。4. 验证请求与成功结果配置写好后先别急着写动画先确认通道是通的。用 curl 打一发curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: 只回复 ok}] }成功时你会看到类似{choices:[{message:{content:ok}}]}的返回。这一步过了说明 Key 和 base URL 没问题接下来动画调试就不会被网络层干扰。然后写动画状态机。核心逻辑和原始思路一致但把时长来源改成「优先用接口返回拿不到用兜底」const voiceConfig { intervalMs: 300, frameCount: 3, defaultDurationMs: 6000 }; let playingEl null; let frameTimer null; let stopTimer null; let frameIndex 1; function startPlay(el, durationMs) { if (playingEl el) { stopPlay(); return; } stopPlay(); playingEl el; frameIndex 1; el.classList.add(playing); frameTimer setInterval(() { frameIndex frameIndex voiceConfig.frameCount ? 1 : frameIndex 1; el.dataset.frame String(frameIndex); }, voiceConfig.intervalMs); const total durationMs 0 ? durationMs : voiceConfig.defaultDurationMs; stopTimer setTimeout(stopPlay, total); } function stopPlay() { if (!playingEl) return; clearInterval(frameTimer); clearTimeout(stopTimer); playingEl.classList.remove(playing); playingEl.dataset.frame 1; playingEl null; }HTML 侧只要保证每个气泡有唯一 id 和方向类div idvoiceDiv1 classvoice-bubble right data-duration6000 onclickstartPlay(this, Number(this.dataset.duration)) 6.0″ /div浏览器里点一下气泡开始跳动再点一下立刻停等 6 秒自动复位。打开 DevTools 的 Elements 面板能看到playing类在切换data-frame在 1/2/3 之间循环这就是成功结果。5. 本篇常见错排查动画停不下来九成是stopTimer没清或者时长传了 0。检查durationMs是否被Number()转成了NaNNaN 0为 false会走兜底但如果兜底也被改小就出问题。在startPlay里加一行console.log(total)最快定位。连点多个气泡前一个还在跳因为playingEl被覆盖了旧节点的定时器没清。上面的stopPlay()在startPlay开头调用就是为了解决这个确保同一时刻只有一个在播。类名切换了但图没变CSS 里.rPlay1/.rPlay2/.rPlay3的background-image路径写错或者被background-size裁掉了。用 DevTools 看 Computed 面板里的background-image实际解析成了什么。接口 401Key 没带Bearer前缀或者复制时多了空格。用echo $TAOTOKEN_API_KEY | wc -c看长度对不对。报错码对照去文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 查。CORS 报错本地file://直接打开 HTML 会跨域。用npx serve或 Vite 起一个本地服务别双击文件。时长和动画对不齐AMR 这类格式的时长在部分浏览器上解析不准建议后端在返回语音元数据时把duration一起给出来前端只负责消费。6. 把通道和动画拆开维护这套骨架跑通后你会发现真正需要长期维护的只有两块一块是voice配置里的节奏参数一块是 TaoToken 的 Key 与 base URL。前者调观感后者调连通性互不干扰。本地调试时用settings.json命令行工具用config.toml生产环境把 Key 换成环境变量注入配置文件本身可以进仓库。需要验证模型返回的测试文案时模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 最省事要新建或轮换 Key去 API Keys https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入参数和错误码细节在文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你后面要把这套动画接进 Claude Code 之类的编码工作流Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 里有对应的额度说明。最后留一个实用技巧把bubbleIntervalMs做成 URL 参数比如?speed200调试时不用改代码就能对比不同节奏找到最接近微信的那一档再写死。
返回列表