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

悬停效果中nav-hidden-text首次序列动画失效问题求助

文本悬停动画首次触发异常修复方案

问题背景

需要实现的文本悬停动画逻辑:

  • 正常状态:nav-text显示在顶部,nav-hidden-text位于容器底部隐藏
  • 悬停时:nav-text的字符逐个上移消失,nav-hidden-text移动到顶部并逐个显示
  • 移出时:恢复初始状态

当前问题:首次触发悬停时,nav-hidden-text的字符序列动画不生效,后续悬停及移出操作均正常。已排查transition/animation配置、JS强制重排、transition-delay设置等,但问题仍存在。

问题根源

首次加载时,.nav-hidden-text .letter未设置明确的初始过渡状态(transform和opacity),浏览器无法检测到从初始状态到hover状态的属性变化,导致过渡动画不触发。同时JS中类名添加和重排的顺序也影响了首次动画的触发时机。

修复方案

1. 补充CSS初始状态

给.nav-hidden-text .letter添加默认的transform和opacity值,明确初始状态:

.nav-hidden-text .letter {
    transform: translateY(100%);
    opacity: 0;
}

2. 调整JS执行顺序

将类名添加操作放在过渡延迟设置和强制重排之后,确保样式更新完成后再触发过渡:

container.addEventListener('mouseenter', () => {
    isHovering = true;
    clearTimeout(timeoutId);

    requestAnimationFrame(() => {
        updateTransitionDelays(); // 先设置延迟
        forceReflow(); // 强制重排更新样式
        container.classList.add('hover'); // 最后添加hover类触发动画
        container.classList.remove('leave');
    });
});

完整修正代码

JavaScript

document.querySelectorAll('.nav-container').forEach(container => {
    let isHovering = false;
    let timeoutId = null;

    function updateTransitionDelays() {
        container.querySelectorAll('.nav-text .letter').forEach((letter, index) => {
            letter.style.transitionDelay = `${index * 0.05}s`;
        });
        container.querySelectorAll('.nav-hidden-text .letter').forEach((letter, index) => {
            letter.style.transitionDelay = `${(index * 0.05) + 0.2}s`;
        });
    }

    function forceReflow() {
        container.offsetHeight; // 触发重排
    }

    container.addEventListener('mouseenter', () => {
        isHovering = true;
        clearTimeout(timeoutId);

        requestAnimationFrame(() => {
            updateTransitionDelays();
            forceReflow();
            container.classList.add('hover');
            container.classList.remove('leave');
        });
    });

    container.addEventListener('mouseleave', () => {
        isHovering = false;

        requestAnimationFrame(() => {
            updateTransitionDelays();
            timeoutId = setTimeout(() => {
                if (!isHovering) {
                    container.classList.add('leave');
                    container.classList.remove('hover');
                }
            }, 50);
        });
    });
});

CSS

.nav-container {
    display: inline-block;
    width: 200px;
    height: 35px;
    position: relative;
    overflow: hidden;
    font-size: 24px;
    text-align: center;
    color: #333;
    font-family: Arial, sans-serif;
}

.nav-link {
    display: block;
    position: relative;
    text-decoration: none;
    color: inherit;
    height: 100%;
}

.nav-text, .nav-hidden-text {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    white-space: nowrap;
    top: 0;
    transition: top 0.4s ease;
}

.nav-text {
    z-index: 1;
}

.nav-hidden-text {
    top: 100%;
    z-index: 0;
    transition: top 0.4s ease;
}

.letter {
    display: inline-block;
    transition: transform 0.4s ease, opacity 0.4s ease;
}

/* 新增初始状态 */
.nav-hidden-text .letter {
    transform: translateY(100%);
    opacity: 0;
}

.nav-container.hover .nav-text .letter {
    transform: translateY(-100%);
    opacity: 0;
}

.nav-container.hover .nav-hidden-text {
    top: 0;
    z-index: 1;
}

.nav-container.hover .nav-hidden-text .letter {
    transform: translateY(0);
    opacity: 1;
}

.nav-container.leave .nav-text .letter {
    transform: translateY(0);
    opacity: 1;
}

.nav-container.leave .nav-hidden-text {
    top: 100%;
    z-index: 0;
}

.nav-container.leave .nav-hidden-text .letter {
    transform: translateY(100%);
    opacity: 0;
}

HTML

<div class="nav-container">
    <a href="#" class="nav-link">
        <div class="nav-text">
            <span class="letter">O</span>
            <span class="letter">r</span>
            <span class="letter">i</span>
            <span class="letter">g</span>
            <span class="letter">i</span>
            <span class="letter">n</span>
            <span class="letter">a</span>
            <span class="letter">l</span>
            <span class="letter"> </span>
            <span class="letter">T</span>
            <span class="letter">e</span>
            <span class="letter">k</span>
            <span class="letter">s</span>
            <span class="letter">t</span>
        </div>
        <div class="nav-hidden-text">
            <span class="letter">N</span>
            <span class="letter">y</span>
            <span class="letter"> </span>
            <span class="letter">T</span>
            <span class="letter">e</span>
            <span class="letter">k</span>
            <span class="letter">s</span>
            <span class="letter">t</span>
        </div>
    </a>
</div>

原理说明

  1. 明确初始状态:给隐藏文本的字符设置默认的transform: translateY(100%)和opacity: 0,让浏览器能识别到hover时的属性变化,从而触发过渡动画。
  2. 调整JS顺序:先设置过渡延迟,强制浏览器重排更新样式,再添加hover类,确保样式变化被正确捕捉,触发首次动画。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:27:32