实现React组件在过渡动画完成后显示/隐藏
Solution: Delay Component Unmount/Mount Until Transition Completes
Great question! Since you already have the height transition working, we just need to add a small state layer to separate the animation phase from the final mount/unmount state. Here's the simplest no-dependency approach tailored to your existing code:
Core Idea
Instead of directly using the collapsed prop to control whether the component returns null, we split the process into two intentional stages:
- Collapse: Trigger the shrink animation first → wait for it to finish → then unmount the component
- Expand: Mount the component first → wait for the DOM to render → then trigger the grow animation
Step-by-Step Implementation
Modified MyClass.tsx Code
We'll add internal state to track animation phases and listen for transition completion events:
import { useState, useEffect, useRef } from 'react'; import { cx } from 'classnames'; // assuming you're using classnames for className merging export const MyClass = ({ items, collapsed = false, className, style, ...tcProps }: MyProps) => { // Controls if the component is rendered in the DOM at all const [isVisible, setIsVisible] = useState(!collapsed); // Tracks if we're actively running a collapse/expand animation const [isAnimating, setIsAnimating] = useState(false); const containerRef = useRef<HTMLDivElement>(null); // Listen for transition end events to finalize state useEffect(() => { const handleTransitionEnd = (e: TransitionEvent) => { // Only react to height transitions (ignore other properties if added later) if (e.propertyName !== 'height') return; // After collapse animation, hide the component if (collapsed) setIsVisible(false); setIsAnimating(false); }; const container = containerRef.current; if (container) { container.addEventListener('transitionend', handleTransitionEnd); } // Clean up the listener when component unmounts or dependencies change return () => { if (container) { container.removeEventListener('transitionend', handleTransitionEnd); } }; }, [collapsed]); // Handle animation triggers when the collapsed prop changes useEffect(() => { if (!collapsed) { // For expand: first render the component, then start the animation setIsVisible(true); // Wait for DOM to update before triggering the expand animation const timer = setTimeout(() => setIsAnimating(true), 0); return () => clearTimeout(timer); } else if (isVisible) { // For collapse: start the shrink animation before hiding the component setIsAnimating(true); } }, [collapsed, isVisible]); // Don't render anything if we've fully collapsed and hidden if (!isVisible) return null; return ( <div ref={containerRef} id="Container" className={cx( cn.myClass, // Apply collapsed class only during active collapse animation (collapsed && isAnimating) && cn.myClassCollapsed, className )} style={{ ...tokens, ...style }} role="presentation" > <ClassChildren {...tcProps} style={{ width: '100%' }}> {supplyChildren()} </ClassChildren> </div> ); };
Key Changes Explained
isVisibleState: Acts as the final toggle for whether the component renders. We only set this tofalseafter the collapse animation finishes.isAnimatingState: Triggers the transition by applying themyClassCollapsedclass during active animation phases.transitionendListener: Detects when the height animation completes, ensuring we only unmount the component after the visual effect finishes.- Timed Expand Trigger: Using
setTimeout(0)ensures we wait for the component to mount in the DOM before starting the expand animation (so the browser has a base height value to animate from).
Your SCSS Remains Unchanged
Since you confirmed the transition already works, your existing styles don't need any updates:
@use 'utils/animation'; .my-class { transition: height animation.duration(500ms) animation.$default-timing-function; flex-shrink: 0; height: 9rem; overflow: hidden; margin-inline-start: 0; margin-inline-end: 0; padding-inline-start: 0; padding-inline-end: 0; } .my-class-collapsed { height: 0; }
Edge Case Handling
- Rapid Toggles: If the user clicks expand/collapse quickly before an animation finishes, the useEffect cleanup functions will remove old event listeners and clear timeouts, preventing state inconsistencies.
- Multiple Transitions: The
propertyNamecheck ensures we only react to height changes, so adding other transitions (like opacity) later won't break the logic.
内容的提问来源于stack exchange,提问作者Wayne Christian
相关产品推荐
相关产品推荐

