React中点击显示子组件的延迟/动画实现方案
如何为React展开/收起组件添加平滑的下滑淡入过渡动画
我正在做一个点击后显示子组件的React组件,目前已经实现了基础的展开/收起功能,但想给子组件加上平滑的下滑和淡入过渡动画。
现有代码如下:
type ExpandCollapseProps = ComponentProps<"div"> & VariantProps<typeof expandCollapseStyles> & { previewText?: string; children: React.ReactNode; } export const ExpandCollapse = forwardRef<HTMLDivElement, ExpandCollapseProps>(({ variant, className, children, previewText, ...props }, ref) => { const [expanded, setExpanded] = useState<boolean>(false); return ( <div className={cn(expandCollapseStyles({ variant, className }))} ref={ref} {...props} onClick={() => setExpanded(!expanded)}> <div className="space-y-2"> <p className="font-medium">{previewText}</p> {expanded && <div className=""> {children} </div> } </div> </div> ) });
目前组件可正常点击展开/收起,但缺少平滑过渡效果,请问最佳实现方式是什么?
补充信息:我使用以下CSS工具包,希望基于它们实现:
tailwindcsstailwind-mergeclass-variance-authorityclsx
⚠️ 不想安装外部组件包,想自己动手做可复用UI组件。
实现方案
核心问题是你现在用{expanded && ...}直接挂载/卸载子组件,这种方式没法触发过渡动画——组件一卸载就没了,CSS没机会执行过渡。正确的做法是始终渲染子组件容器,通过CSS属性的变化来控制显示状态,配合Tailwind的过渡类实现平滑动画。
具体步骤及代码修改
- 移除条件渲染逻辑,始终保留子组件的外层容器
- 用
max-h(最大高度)模拟下滑动画,配合overflow-hidden防止内容溢出 - 用
opacity实现淡入淡出效果 - 加上Tailwind的过渡类控制动画时长和曲线
修改后的完整代码:
import { forwardRef, useState } from 'react'; import { cn } from '@/lib/utils'; // 假设cn是你封装的tailwind-merge+clsx组合函数 import { expandCollapseStyles } from './expand-collapse.styles'; type ExpandCollapseProps = ComponentProps<"div"> & VariantProps<typeof expandCollapseStyles> & { previewText?: string; children: React.ReactNode; } export const ExpandCollapse = forwardRef<HTMLDivElement, ExpandCollapseProps>(({ variant, className, children, previewText, ...props }, ref) => { const [expanded, setExpanded] = useState<boolean>(false); return ( <div className={cn(expandCollapseStyles({ variant, className }))} ref={ref} {...props} onClick={() => setExpanded(!expanded)} > <div className="space-y-2"> <p className="font-medium">{previewText}</p> {/* 始终渲染内容容器,通过类名切换控制过渡 */} <div className={cn( "overflow-hidden transition-all duration-300 ease-in-out", expanded ? "max-h-[1000px] opacity-100" : "max-h-0 opacity-0" )} > {/* 加内边距避免内容紧贴预览文本 */} <div className="pt-2"> {children} </div> </div> </div> </div> ) });
优化点:精准控制高度
上面代码用max-h-[1000px]是简化写法,如果你的子组件内容高度可能超过这个值,会被截断。可以用useRef和useEffect获取真实内容高度,实现更精准的过渡:
import { forwardRef, useState, useRef, useEffect } from 'react'; // ... 其他导入不变 export const ExpandCollapse = forwardRef<HTMLDivElement, ExpandCollapseProps>(({ variant, className, children, previewText, ...props }, ref) => { const [expanded, setExpanded] = useState<boolean>(false); const contentRef = useRef<HTMLDivElement>(null); const [contentHeight, setContentHeight] = useState(0); // 监听子内容变化,更新真实高度 useEffect(() => { if (contentRef.current) { setContentHeight(contentRef.current.scrollHeight); } }, [children]); return ( <div className={cn(expandCollapseStyles({ variant, className }))} ref={ref} {...props} onClick={() => setExpanded(!expanded)} > <div className="space-y-2"> <p className="font-medium">{previewText}</p> <div ref={contentRef} className={cn( "overflow-hidden transition-all duration-300 ease-in-out", expanded ? `max-h-[${contentHeight}px] opacity-100` : "max-h-0 opacity-0" )} > <div className="pt-2"> {children} </div> </div> </div> </div> ) });
说明
transition-all让所有可过渡的属性(max-h、opacity)都参与动画duration-300控制动画时长为300ms,可根据需求调整(比如duration-500)ease-in-out让动画开头和结尾更平缓- 收起时
max-h-0会把容器高度压缩为0,配合overflow-hidden隐藏内容,展开时恢复最大高度,实现下滑效果
内容的提问来源于stack exchange,提问作者clxrity
相关产品推荐
相关产品推荐

