React + Tailwind CSS:移动端侧边栏关闭过渡效果实现求助
侧边栏关闭时无法触发过渡效果的解决方案
我已实现点击汉堡图标时侧边栏带过渡效果显示,但点击关闭按钮时无法触发过渡效果。尝试过条件渲染操作但无效,以下是最新代码实现,恳请提供实现过渡效果的建议。
效果演示
现有代码
Home.jsx
/* notes: date created: 15/03/2023 */ import React, { useState, useRef, useEffect } from 'react'; import { Sidebar, UserProfile } from '../components'; import Pins from './Pins'; import { client } from '../client'; import myLogo from '../assets/logoCropped.png'; import { userQuery, fetchUserFromLocalStorage } from '../utils/data'; import { HiMenu } from 'react-icons/hi'; import { AiFillCloseCircle } from 'react-icons/ai'; import { Link, Route, Routes } from 'react-router-dom'; // TODO: added this along the way. continue doing this until it works const hiddenSidebar = 'fixed w-1/2 bg-black h-screen overflow-y-auto shadow-md z-10 animate-slide-in duration-75'; const activeSidebar = 'bg-red-500 z-10 transition transition-all '; const Home = () => { const [toggleSideBar, setToggleSideBar] = useState(false); const [user, setUser] = useState(null); const scrollRef = useRef(null); const userInfo = fetchUserFromLocalStorage(); // set up scroll to be at the top of the sidebar useEffect(() => { scrollRef.current.scrollTo(0, 0) }, []) // retrieve data using a query from data.js useEffect(() => { const query = userQuery(userInfo?.sub) client.fetch(query) .then((data) => { setUser(data[0]); }) }) return ( <div className='flex bg-gray-50 md:flex-row flex-col h-screen transition-height duration-75 ease-out'> {/* desktop sidebar */} <div className='hidden md:flex h-screen flex-initial'> <Sidebar user={user && user} /> </div> {/* mobile sidebar */} <div className='flex md:hidden flex-row'> <div className='p-2 w-full flex flex-row justify-between items-center shadow-md'> <HiMenu fontSize={40} className="cursor-pointer" onClick={() => setToggleSideBar(true)} /> <Link to='/'> <img src={myLogo} alt="logo" className='w-16' /> </Link> <Link to={`user-profile/${user?._id}`}> <img src={user?.image} alt="logo" className='w-16' /> </Link> </div> { toggleSideBar && ( <div className={toggleSideBar ? hiddenSidebar : activeSidebar}> <div className='absolute w-full flex justify-end items-center p-2 '> <AiFillCloseCircle fontSize={30} className="cursor-pointer" onClick={() => { setToggleSideBar(false) } } /> </div> <Sidebar user={user && user} closeToggle={setToggleSideBar} /> </div> )} </div> {/* routes */} <div className='pb-2 flex-1 h-screen overflow-y-scroll' ref={scrollRef}> <Routes> <Route path="user-profile/:userId" element={<UserProfile />} /> <Route path='/*' element={<Pins user={user && user} />} /> </Routes> </div> </div> ) } export default Home
tailwind.config.js
/** @type {import('tailwindcss').Config} */ module.exports = { purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], darkMode: false, // or media or class content: [], theme: { extend: { margin: { 320: '320px', }, width: { 190: '190px', 275: '275px', 300: '300px', 340: '340px', 350: '350px', 656: '656px', 880: '880px', 508: '508px', }, height: { 80: '80px', 340: '340px', 370: '370px', 420: '420px', 510: '510px', 600: '600px', 685: '685px', 800: '800px', '90vh': '90vh', }, flex: { 0.7: '0.7 1 0%', }, maxHeight: { 370: '370px', }, // further tweeks minWidth: { 210: '210px', 350: '350px', 620: '620px', }, // change this for personalization textColor: { lightGray: '#F1EFEE', primary: '#FAFAFA', secColor: '#efefef', navColor: '#BEBEBE', }, // change this for personalization backgroundColor: { mainColor: '#FBF8F9', secondaryColor: '#F0F0F0', blackOverlay: 'rgba(0, 0 ,0 ,0.7)', }, /* TODO: create slide-out keyframne and animation config for tailwindcss basically a reverse of slide in */ keyframes: { 'slide-in': { '0%': { '-webkit-transform': 'translateX(-200px)', transform: 'translateX(-200px)', }, '100%': { '-webkit-transform': 'translateX(0px)', transform: 'translateX(0px)', }, }, // TODO: added this out 'slide-out': { '0%': { '-webkit-transform': 'translateX(0px)', transform: 'translateX(0px)', }, '100%': { '-webkit-transform': 'translateX(-200px)', transform: 'translateX(-200px)', }, }, 'slide-fwd': { '0%': { '-webkit-transform': 'translateZ(0px)', transform: 'translateZ(0px)', }, '100%': { '-webkit-transform': 'translateZ(160px)', transform: 'translateZ(160px)', }, }, }, animation: { 'slide-in': 'slide-in 0.5s ease-out', 'slide-fwd': 'slide-fwd 0.45s cubic-bezier(0.250, 0.460, 0.450, 0.940) both', // TODO: added this 'slide-out' : 'slide-out 0.5s ease-out', }, transitionProperty: { height: 'height', }, }, cursor: { 'zoom-in': 'zoom-in', pointer: "pointer", }, }, variants: { extend: { } }, plugins: [], };
问题原因及解决方案
核心问题
当前代码通过toggleSideBar && (...)直接卸载侧边栏组件,关闭时组件被立即移除,没有时间执行过渡动画;同时类名逻辑错误,导致关闭动画无法触发。
修改步骤
- 添加关闭状态跟踪
新增状态标记侧边栏是否正在执行关闭动画,避免组件被立即卸载:
const [isClosing, setIsClosing] = useState(false);
- 调整关闭逻辑
点击关闭按钮时先触发关闭动画,动画结束后再隐藏组件:
const handleCloseSidebar = () => { setIsClosing(true); }; const handleAnimationEnd = () => { setIsClosing(false); setToggleSideBar(false); };
- 修正类名与动画绑定
根据状态动态切换动画类,确保显示/关闭时分别触发对应的动画:
{toggleSideBar && ( <div className={`fixed w-1/2 bg-black h-screen overflow-y-auto shadow-md z-10 ${ isClosing ? 'animate-slide-out' : 'animate-slide-in' }`} onAnimationEnd={handleAnimationEnd} > <div className='absolute w-full flex justify-end items-center p-2 '> <AiFillCloseCircle fontSize={30} className="cursor-pointer" onClick={handleCloseSidebar} /> </div> <Sidebar user={user && user} closeToggle={handleCloseSidebar} /> </div> )}
- 移除无效类定义
删除原来的hiddenSidebar和activeSidebar变量,避免混淆。
原理说明
通过isClosing状态延迟组件卸载,让关闭动画完整执行;利用onAnimationEnd事件监听动画结束,再正式隐藏组件,确保过渡效果正常触发。
内容的提问来源于stack exchange,提问作者aaron dalao
相关产品推荐
相关产品推荐

