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

JS切换CSS动画实现字母悬停旋转变色时第二次动画失效如何解决

问题原因

  • CSS语法存在错误:@keyframes定义结束后多余的分号、括号闭合逻辑异常,导致unrotate动画没有被浏览器正确解析。
  • 动画触发逻辑缺陷:直接切换元素的动画类名时,浏览器会默认保留元素之前的动画完成态,不会重新触发新动画,需要强制触发重排重置动画状态。
  • 非标准属性兼容问题:你使用的ind.path[0]属于非标准属性,部分浏览器无法正常识别,会导致获取元素失败。

修复代码

修正后CSS代码

@import url("https://fonts.googleapis.com/css2?family=Luckiest+Guy&family=Roboto:wght@400;700&display=swap");

:root {
  --font-color: rgb(243, 14, 224);
}

.landing-text {
  font-family: "Luckiest Guy", cursive;
  flex-wrap: wrap;
  height: 100vh;
  font-size: 6vw;
  margin-top: 40vh;
  margin-left: 4rem;
}
.landing-text div{
    display: flex;
    max-height: 120px;
}
/* 统一span默认样式,避免动画跳变 */
.landing-text span {
  cursor: pointer;
  transform: rotateY(0deg);
  color: #000;
}
.rotate {  
    animation: rotate 2s forwards;
}
  
@keyframes rotate {
  100% {
    transform: rotateY(720deg);
    color: var(--font-color);
  }
}

.unrotate {
  animation: unrotate 2s forwards;
}
@keyframes unrotate {
  100%{
    transform: rotateY(720deg);
    color: black;
  }
}

修正后JS代码

const landingEl = document.getElementById("landing");
const landingText = document.getElementById("landing-text");
const landingFraze = `Lorem ipsum dolor sit amet consectetur adipisicing elit.`;

const spans = document.getElementsByTagName('span');

let letterList = [];
let text;

function rotate(e) {
  // 用标准属性直接获取触发事件的元素
  const spiningLeter = e.currentTarget;
  let oldClass = ''
  // 先移除旧动画类
  if(spiningLeter.classList.contains('rotate')) {
    oldClass = 'rotate'
    spiningLeter.classList.remove('rotate')
  } else if(spiningLeter.classList.contains('unrotate')) {
    oldClass = 'unrotate'
    spiningLeter.classList.remove('unrotate')
  }
  // 强制触发重排,重置动画状态
  void spiningLeter.offsetWidth;
  // 切换新动画类
  spiningLeter.classList.add(oldClass === 'rotate' || oldClass === '' ? 'unrotate' : 'rotate')
}

function everyLeter() {
  let text = landingFraze;
  let sentenceList = text.split(".");
  sentenceList.forEach((element, i) => {
    if(!element.trim()) return // 过滤分割产生的空字符串
    let div = document.createElement('div');
    let list = element.split("");
    list.forEach((leter, index) => {
      let span = document.createElement("span");
      span.id = `${index}a${i}`;
      span.addEventListener('mouseenter', rotate);
      let leterP = document.createElement('p');
      if(leter === ' ') {
        leterP.style.marginLeft = '20px'
      }
      if(list.length === index + 1){
        leterP.textContent = `${leter}.`;
     
      } else {
        leterP.textContent = leter;
        
      }
      span.appendChild(leterP);
      div.appendChild(span);
      
  });
    landingText.appendChild(div);
  
  });
}

everyLeter();

核心修复说明

  • 清理了CSS语法错误,保证两个动画都能被浏览器正常识别
  • 移除旧动画类后通过void 元素.offsetWidth触发浏览器重排,清空之前的动画缓存,保证新动画可以正常触发
  • 替换非标准的event.path为标准的event.currentTarget,兼容全量浏览器
  • 过滤了字符串分割产生的空值,避免生成多余的空DOM节点

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 07:45:02