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

类名相似时CSS动画无法运行的问题排查求助

类名相似元素的动画无法正常运行

我想实现和下面示例代码类似的效果,但实际代码中类名相似的元素(第二个.herowrap里的.top-text和.btm-text)完全没动画,折腾好几个小时都没解决,求原因和修复方法。


示例HTML & CSS代码

(这个示例里两个.rocket-animation元素都能正常动)

@keyframes rocket {
    0%   {left:0%; top:0;}
    100% {left:100%; top:0;}
}
@keyframes rocket-to-bottom-right {
    0%   {left:0%; top:0;}
    100% {left:100%; top:500px;}
}

.rocket-animation {
    position: relative;
    animation-name: rocket;
    animation-duration: 5s;
    animation-delay: 2s;
    animation-iteration-count: infinite;
    animation-direction: alternate;
}
<div class="rocket-animation"><i class="rocket" aria-hidden="true">🚀</i></div>

<div class="parent">
  <div class="rocket-animation"><i class="rocket" aria-hidden="true">🚀</i></div>
</div>

我的实际代码

第二个.herowrap里的文本完全没动画效果,第一个的动画也有异常。

JavaScript代码

var topText = document.querySelector(".top-text");
var btmText = document.querySelector(".btm-text");

function topSpanGenerator(arrNew) {
  for (i = 0; i < arrNew.length; i++) {
    var span = document.createElement("span");
    span.setAttribute("class", "letter");
    topText.appendChild(span);
    span.innerHTML = arrNew[i];
  }
}

function btmSpanGenerator(arrNew) {
  for (i = 0; i < arrNew.length; i++) {
    var span = document.createElement("span");
    span.setAttribute("class", "letter");
    btmText.appendChild(span);
    span.innerHTML = arrNew[i];
  }
}

// 动画结束后删除span
function removeArrayItems() {
  topText.addEventListener("animationend", function(e) {
    setTimeout(function() {
      e.target.remove();
    }, 0.5);
  });

  btmText.addEventListener("animationend", function(e) {
    setTimeout(function() {
      e.target.remove();
    }, 0.5);
  });
}

// 顶部文本生成
function topTextGenerator() {
  setTimeout(function() {
    topSpanGenerator(["w", "e", "b", "s", "i", "t", "e"]);
    removeArrayItems();
  }, 500);
  setTimeout(function() {
    topSpanGenerator(["g", "e", "n", "t", "l", "e"]);
    removeArrayItems();
  }, 6000);
  setTimeout(function() {
    topSpanGenerator(["d", "i", "g", "i", "t", "a", "l"]);
    removeArrayItems();
  }, 11500);
  setTimeout(function() {
    topSpanGenerator(["g", "a", "m", "i", "n", "g"]);
    removeArrayItems();
  }, 17000);
}
topTextGenerator();

// 底部文本生成
function btmTextGenerator() {
  setTimeout(function() {
    btmSpanGenerator(["w", "i", "z", "a", "r", "d", "r", "y"]);
    removeArrayItems();
  }, 800);
  setTimeout(function() {
    btmSpanGenerator(["g", "i", "a", "n", "t"]);
    removeArrayItems();
  }, 6300);
  setTimeout(function() {
    btmSpanGenerator(["o", "v", "e", "r", "l", "o", "r", "d"]);
    removeArrayItems();
  }, 11800);
  setTimeout(function() {
    btmSpanGenerator(["e", "n", "t", "h", "u", "s", "i", "a", "s", "t"]);
    removeArrayItems();
  }, 17300);
}
btmTextGenerator();

// 无限循环动画
function loop() {
  let functionFinish = false;
  if (!functionFinish) {
    setInterval(function() {
      topTextGenerator();
    }, 22000);
  }

  if (!functionFinish) {
    setInterval(function() {
      btmTextGenerator();
    }, 22000);
  }
}
loop();

CSS代码

@keyframes fade {
  0% {
    transform: translateY(100px);
    transform: translateZ(2);
    opacity: 0;
  }

  50% {
    transform: translateY(0);
    opacity: 1;
  }

  100% {
    transform: translateY(-100px);
    transform: translateZ(1);
    opacity: 0;
  }
}

/* 应用淡入淡出动画 */
.tricks span {
  position: relative;
  animation-delay: 5s;
  -webkit-animation-delay: 5s;
  -moz-animation-delay: 5s;
  animation: fade 5s cubic-bezier(0.06, 0.83, 0.24, 1) normal;
  -webkit-animation: fade 5s cubic-bezier(0.06, 0.83, 0.24, 1) normal;
  -moz-animation: fade 5s cubic-bezier(0.06, 0.83, 0.24, 1) normal;
  animation-fill-mode: forwards;
  -webkit-animation-fill-mode: forwards;
  -moz-animation-fill-mode: forwards;
  opacity: 0;
}

.tricks span:nth-child(0) {
  animation-delay: 0.1s;
  -webkit-animation-delay: 0.1s;
  -moz-animation-delay: 0.1s;
}

.tricks span:nth-child(1) {
  animation-delay: 0.15s;
  -webkit-animation-delay: 0.15s;
  -moz-animation-delay: 0.15s;
}

.tricks span:nth-child(2) {
  animation-delay: 0.2s;
  -webkit-animation-delay: 0.2s;
  -moz-animation-delay: 0.2s;
}

.tricks span:nth-child(3) {
  animation-delay: 0.25s;
  -webkit-animation-delay: 0.25s;
  -moz-animation-delay: 0.25s;
}

.tricks span:nth-child(4) {
  animation-delay: 0.3s;
  -webkit-animation-delay: 0.3s;
  -moz-animation-delay: 0.3s;
}

.tricks span:nth-child(5) {
  animation-delay: 0.35s;
  -webkit-animation-delay: 0.35s;
  -moz-animation-delay: 0.35s;
}

.tricks span:nth-child(6) {
  animation-delay: 0.4s;
  -webkit-animation-delay: 0.4s;
  -moz-animation-delay: 0.4s;
}

.tricks span:nth-child(7) {
  animation-delay: 0.45s;
  -webkit-animation-delay: 0.45s;
  -moz-animation-delay: 0.45s;
}

.tricks span:nth-child(8) {
  animation-delay: 0.5s;
  -webkit-animation-delay: 0.5s;
  -moz-animation-delay: 0.5s;
}

.tricks span:nth-child(9) {
  animation-delay: 0.55s;
  -webkit-animation-delay: 0.55s;
  -moz-animation-delay: 0.55s;
}

.tricks span:nth-child(10) {
  animation-delay: 0.6s;
  -webkit-animation-delay: 0.6s;
  -moz-animation-delay: 0.6s;
}

HTML代码

<div class="herowrap">
  <p class="ab__hero-sub">sample text</p>
  <h1 class="top-text tricks"></h1>
  <h1 class="btm-text tricks"></h1>
</div>

<div class="parent">
  <div class="herowrap">
    <p class="ab__hero-sub">sample text</p>
    <h1 class="top-text tricks"></h1>
    <h1 class="btm-text tricks"></h1>
  </div>
</div>

问题原因及修复方案

1. 选择器只选中第一个匹配元素

document.querySelector(".top-text")和document.querySelector(".btm-text")只会返回页面中第一个符合类名的元素,第二个.herowrap里的对应元素完全没被JS操作,自然不会生成span和动画。

修复:改用document.querySelectorAll获取所有匹配元素,然后遍历处理:

// 替换原来的topText和btmText定义
var topTexts = document.querySelectorAll(".top-text");
var btmTexts = document.querySelectorAll(".btm-text");

// 修改topSpanGenerator,接收要操作的元素作为参数
function topSpanGenerator(targetEl, arrNew) {
  for (let i = 0; i < arrNew.length; i++) {
    var span = document.createElement("span");
    span.setAttribute("class", "letter");
    targetEl.appendChild(span);
    span.innerHTML = arrNew[i];
  }
}

// 同理修改btmSpanGenerator
function btmSpanGenerator(targetEl, arrNew) {
  for (let i = 0; i < arrNew.length; i++) {
    var span = document.createElement("span");
    span.setAttribute("class", "letter");
    targetEl.appendChild(span);
    span.innerHTML = arrNew[i];
  }
}

// 修改topTextGenerator,遍历所有topTexts
function topTextGenerator() {
  topTexts.forEach(el => {
    setTimeout(function() {
      topSpanGenerator(el, ["w", "e", "b", "s", "i", "t", "e"]);
      bindRemoveEvent(el);
    }, 500);
    setTimeout(function() {
      topSpanGenerator(el, ["g", "e", "n", "t", "l", "e"]);
      bindRemoveEvent(el);
    }, 6000);
    setTimeout(function() {
      topSpanGenerator(el, ["d", "i", "g", "i", "t", "a", "l"]);
      bindRemoveEvent(el);
    }, 11500);
    setTimeout(function() {
      topSpanGenerator(el, ["g", "a", "m", "i", "n", "g"]);
      bindRemoveEvent(el);
    }, 17000);
  });
}

// 同理修改btmTextGenerator
function btmTextGenerator() {
  btmTexts.forEach(el => {
    setTimeout(function() {
      btmSpanGenerator(el, ["w", "i", "z", "a", "r", "d", "r", "y"]);
      bindRemoveEvent(el);
    }, 800);
    setTimeout(function() {
      btmSpanGenerator(el, ["g", "i", "a", "n", "t"]);
      bindRemoveEvent(el);
    }, 6300);
    setTimeout(function() {
      btmSpanGenerator(el, ["o", "v", "e", "r", "l", "o", "r", "d"]);
      bindRemoveEvent(el);
    }, 11800);
    setTimeout(function() {
      btmSpanGenerator(el, ["e", "n", "t", "h", "u", "s", "i", "a", "s", "t"]);
      bindRemoveEvent(el);
    }, 17300);
  });
}

2. CSS的:nth-child(0)无效

CSS的:nth-child()选择器是从1开始计数的,所以.tricks span:nth-child(0)不会匹配任何元素,第一个span的延迟样式没生效。

修复:把:nth-child(0)改成:nth-child(1),后面的序号依次对应即可。

3. 动画中transform重复赋值被覆盖

在@keyframes fade里,每个关键帧里写了两次transform,后面的会覆盖前面的,比如0%里translateY(100px)会被translateZ(2)覆盖,导致Y轴位移失效。

修复:把多个transform合并成一行:

@keyframes fade {
  0% {
    transform: translateY(100px) translateZ(2);
    opacity: 0;
  }

  50% {
    transform: translateY(0);
    opacity: 1;
  }

  100% {
    transform: translateY(-100px) translateZ(1);
    opacity: 0;
  }
}

4. 重复绑定animationend事件

每次调用removeArrayItems都会给同一个元素绑定新的animationend监听,触发时会多次执行删除逻辑,导致元素异常消失。

修复:把事件绑定改成只执行一次,或者封装成独立函数,绑定前先移除旧监听:

// 替换原来的removeArrayItems函数
function bindRemoveEvent(el) {
  // 先移除旧监听,避免重复绑定
  el.removeEventListener("animationend", handleAnimationEnd);
  el.addEventListener("animationend", handleAnimationEnd);
}

function handleAnimationEnd(e) {
  // setTimeout的时间单位是毫秒,0.5毫秒几乎没延迟,改成500毫秒更合理
  setTimeout(function() {
    e.target.remove();
  }, 500);
}

5. 循环函数里定时器叠加

loop函数里的functionFinish变量永远是false,每次调用loop都会新增两个setInterval,导致定时器越来越多,生成大量重复span。

修复:只初始化一次定时器,或者用setTimeout递归调用代替setInterval,避免叠加:

function loop() {
  // 只启动一次定时器,不需要functionFinish变量
  setInterval(function() {
    topTextGenerator();
  }, 22000);

  setInterval(function() {
    btmTextGenerator();
  }, 22000);
}
// 直接调用一次即可,不需要重复调用
loop();

另外,注意循环里的时间要和文本生成的总时长匹配,避免文本还没显示完就开始新一轮生成。


内容的提问来源于stack exchange,提问作者zack_110

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 17:09:47