JavaScript文本循环动画异常:重置索引后Bug修复咨询
循环打字/删除文本动画异常修复及优化方案
问题说明
需要实现循环动画:逐个字符添加文本 → 逐个字符删除文本 → 切换新文本,重复该流程。但在重置stackIndex和i并重启动画时,出现文本混乱、跳转异常的问题。
当前代码
HTML
<h1 id="title">Desenvolvedor </h1>
JavaScript
const title = document.getElementById("title"); const stacks = ["Front-End", "Back-End", "Full Stack"]; const speed = 200; let stackIndex = 0; let i = 0; function addChar() { setTimeout(() => { title.innerHTML += stacks[stackIndex].charAt(i); i++; typeWriter(stackIndex); }, speed); } function deleteChar() { setTimeout(() => { title.innerHTML = title.innerHTML.slice(0, -1); if (title.innerText.length > 13) { deleteChar(); } else { if (stackIndex > 1) { i = 0; stackIndex = 0; typeWriter(stackIndex); } i = 0; typeWriter(stackIndex++); } }, speed); } function typeWriter(stackIndex) { if (i < stacks[stackIndex].length) { addChar(); } else { deleteChar(); } }
问题根源
- 异步定时器未清理:
setTimeout是异步任务,重置索引时,之前的定时器回调仍在队列中等待执行,会干扰新的动画流程。 - 硬编码文本长度:判断
title.innerText.length > 13依赖初始文本的固定长度,一旦初始文本修改就会出错。 - 逻辑分支错误:
deleteChar中,无论是否重置stackIndex,都会执行stackIndex++,导致重置后立刻跳转到下一个索引,出现异常。
修复方案
const title = document.getElementById("title"); const stacks = ["Front-End", "Back-End", "Full Stack"]; const speed = 200; const initialText = "Desenvolvedor "; // 保存初始文本,避免硬编码长度 let stackIndex = 0; let i = 0; // 封装延迟函数,简化异步流程 function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function addChar() { while (i < stacks[stackIndex].length) { title.innerHTML += stacks[stackIndex].charAt(i); i++; await delay(speed); } deleteChar(); } async function deleteChar() { while (title.innerText.length > initialText.length) { title.innerHTML = title.innerHTML.slice(0, -1); await delay(speed); } // 循环切换文本索引 stackIndex = (stackIndex + 1) % stacks.length; i = 0; addChar(); } // 启动动画 addChar();
修复要点
- 用
async/await替代嵌套定时器,逻辑更线性,避免回调地狱。 - 保存初始文本长度,动态判断删除终止条件,摆脱硬编码限制。
- 使用取余运算实现索引循环,无需手动判断边界,逻辑更简洁。
更简洁的可复用实现
可以封装为通用函数,支持自定义初始文本、文本列表和动画速度:
async function typewriterLoop(element, initialText, textList, speed = 200) { let currentIndex = 0; element.innerHTML = initialText; const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); while (true) { // 打字阶段 const currentText = textList[currentIndex]; for (const char of currentText) { element.innerHTML += char; await delay(speed); } // 打字完成后停顿(可选优化) await delay(speed * 3); // 删除阶段 while (element.innerHTML.length > initialText.length) { element.innerHTML = element.innerHTML.slice(0, -1); await delay(speed); } // 切换到下一个文本 currentIndex = (currentIndex + 1) % textList.length; await delay(speed); } } // 调用示例 typewriterLoop( document.getElementById("title"), "Desenvolvedor ", ["Front-End", "Back-End", "Full Stack"], 200 );
这个版本模块化程度更高,便于维护和复用,同时加入了打字完成后的停顿,动画体验更自然。
内容的提问来源于stack exchange,提问作者vinícios
相关产品推荐
相关产品推荐

