类名相似时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

