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;
关键修改点
- 拆分状态:用
isSidebarExpanded控制侧边栏宽度,showText控制文本显示,避免两者同步触发。 - 延迟触发:鼠标进入时,先展开侧边栏,等待500ms(和侧边栏动画时长一致)后显示文本;鼠标离开时,先隐藏文本,再延迟收起侧边栏,让过渡更自然。
- 文本过渡:给文本添加
transition-opacity duration-300,让文本显示/隐藏时有淡入淡出效果,进一步提升流畅度。 - 内存泄漏防护:用
useEffect清除定时器,避免组件卸载后定时器仍在运行。
内容的提问来源于stack exchange,提问作者Krishna Biswal
相关产品推荐
相关产品推荐

