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

ReactJS侧边栏动画问题:实现文本延迟显示至展开动画完成

React 动画侧边栏文本延迟显示解决方案

问题说明

我正在用ReactJS实现一个动画侧边栏,需求是:

  • 默认状态:仅显示图标,侧边栏宽度较窄
  • 鼠标悬停时:侧边栏展开,同时显示图标和文本

目前侧边栏展开功能已完成,但悬停时文本会突兀出现,希望实现侧边栏展开动画完成后再显示文本的效果。

原代码

import React, { useState } from 'react';
import { Outlet, NavLink, Link } from 'react-router-dom';

//shadcn UI imports
import { ScrollArea } from '@/components/ui/scroll-area';

//react icons imports
import { FaUser } from 'react-icons/fa';
import { IoSettings } from 'react-icons/io5';
import { FaSearch } from 'react-icons/fa';
import { BiMessageAltDetail } from 'react-icons/bi';
import { BiLogOutCircle } from 'react-icons/bi';
import { MdPostAdd } from 'react-icons/md';
import { RiDashboardHorizontalFill } from 'react-icons/ri';

function Clientlayout() {
  const [isHovered, setIsHovered] = useState(false);

  return (
    <div
      className={` mx-auto max-h-screen grid ${isHovered ? 'grid-cols-[2fr_8fr]' : 'grid-cols-[1fr_25fr]'} gap-4 transition-all duration-500`}
    >
      <div
        className="max-h-[99vh] my-2 rounded-lg border-2"
        onMouseEnter={() => setIsHovered(true)}
        onMouseLeave={() => setIsHovered(false)}
      >
        <div className=" px-3 flex pt-16 gap-2 flex-col overflow-hidden ">
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? ' bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : ' rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <FaUser className="text-base m-1" />
            {isHovered && <p className="text-base">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? ' bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : ' rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <MdPostAdd className="text-xl m-1" />
            {isHovered && <p className="text-base">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? ' bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : ' rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <BiMessageAltDetail className="text-xl m-1" />
            {isHovered && <p className="text-base">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? ' bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : ' rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <FaSearch className="text-base m-1" />
            {isHovered && <p className="text-base">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? ' bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : ' rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <RiDashboardHorizontalFill className="text-lg m-1" />
            {isHovered && <p className="text-base">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? ' bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : ' rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <IoSettings className="text-base m-1" />
            {isHovered && <p className="text-base">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? ' bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : ' rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <BiLogOutCircle className="text-lg m-1" />
            {isHovered && <p className="text-base">abc</p>}
          </NavLink>
        </div>
      </div>
      <ScrollArea className="h-screen">
        <Outlet />
      </ScrollArea>
    </div>
  );
}

export default Clientlayout;

解决方案

核心思路是拆分状态,用延迟触发文本显示,和侧边栏的动画时长(500ms)保持一致,确保动画完成后文本才出现。

修改后的代码

import React, { useState, useEffect } from 'react';
import { Outlet, NavLink, Link } from 'react-router-dom';

//shadcn UI imports
import { ScrollArea } from '@/components/ui/scroll-area';

//react icons imports
import { FaUser } from 'react-icons/fa';
import { IoSettings } from 'react-icons/io5';
import { FaSearch } from 'react-icons/fa';
import { BiMessageAltDetail } from 'react-icons/bi';
import { BiLogOutCircle } from 'react-icons/bi';
import { MdPostAdd } from 'react-icons/md';
import { RiDashboardHorizontalFill } from 'react-icons/ri';

function Clientlayout() {
  const [isSidebarExpanded, setIsSidebarExpanded] = useState(false);
  const [showText, setShowText] = useState(false);
  const ANIMATION_DURATION = 500; // 和侧边栏transition时长一致

  // 处理鼠标进入:先展开侧边栏,延迟后显示文本
  const handleMouseEnter = () => {
    setIsSidebarExpanded(true);
    setTimeout(() => {
      setShowText(true);
    }, ANIMATION_DURATION);
  };

  // 处理鼠标离开:先隐藏文本,延迟后收起侧边栏
  const handleMouseLeave = () => {
    setShowText(false);
    setTimeout(() => {
      setIsSidebarExpanded(false);
    }, 100); // 给文本隐藏留一点过渡时间
  };

  // 组件卸载时清除定时器,避免内存泄漏
  useEffect(() => {
    let enterTimer, leaveTimer;
    return () => {
      clearTimeout(enterTimer);
      clearTimeout(leaveTimer);
    };
  }, []);

  return (
    <div
      className={`mx-auto max-h-screen grid ${isSidebarExpanded ? 'grid-cols-[2fr_8fr]' : 'grid-cols-[1fr_25fr]'} gap-4 transition-all duration-${ANIMATION_DURATION}`}
    >
      <div
        className="max-h-[99vh] my-2 rounded-lg border-2"
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
        <div className="px-3 flex pt-16 gap-2 flex-col overflow-hidden">
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? 'bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : 'rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <FaUser className="text-base m-1" />
            {showText && <p className="text-base transition-opacity duration-300">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? 'bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : 'rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <MdPostAdd className="text-xl m-1" />
            {showText && <p className="text-base transition-opacity duration-300">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? 'bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : 'rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <BiMessageAltDetail className="text-xl m-1" />
            {showText && <p className="text-base transition-opacity duration-300">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? 'bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : 'rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <FaSearch className="text-base m-1" />
            {showText && <p className="text-base transition-opacity duration-300">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? 'bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : 'rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <RiDashboardHorizontalFill className="text-lg m-1" />
            {showText && <p className="text-base transition-opacity duration-300">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? 'bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : 'rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <IoSettings className="text-base m-1" />
            {showText && <p className="text-base transition-opacity duration-300">abc</p>}
          </NavLink>
          <NavLink
            to="/client/abc"
            className={({ isActive }) =>
              isActive
                ? 'bg-slate-900 rounded-md p-2 border-blue-600 flex gap-2 items-center'
                : 'rounded-md p-2 hover:bg-slate-900 flex gap-2 items-center'
            }
          >
            <BiLogOutCircle className="text-lg m-1" />
            {showText && <p className="text-base transition-opacity duration-300">abc</p>}
          </NavLink>
        </div>
      </div>
      <ScrollArea className="h-screen">
        <Outlet />
      </ScrollArea>
    </div>
  );
}

export default Clientlayout;

关键修改点

  1. 拆分状态:用isSidebarExpanded控制侧边栏宽度,showText控制文本显示,避免两者同步触发。
  2. 延迟触发:鼠标进入时,先展开侧边栏,等待500ms(和侧边栏动画时长一致)后显示文本;鼠标离开时,先隐藏文本,再延迟收起侧边栏,让过渡更自然。
  3. 文本过渡:给文本添加transition-opacity duration-300,让文本显示/隐藏时有淡入淡出效果,进一步提升流畅度。
  4. 内存泄漏防护:用useEffect清除定时器,避免组件卸载后定时器仍在运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 01:59:52