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

React/Joy-UI折叠列表动画问题:cubic-bezier修复退出却破坏进入

React/Joy-UI折叠列表动画:进出动画异常解决

我正在尝试用React/Joy-UI实现折叠列表动画,当前Transition组件代码如下:

<Transition nodeRef={nodeRef} in={browseOpen} timeout={1000}>
  {(state: string) => (
    <List
      aria-labelledby="nav-list-browse"
      sx={{
        '& .JoyListItemButton-root': { p: '8px' },
        transition: '1000ms',
        transitionProperty: 'max-height',
        overflow: 'hidden',
        ...{
          exiting: { maxHeight: '0px'},
          exited: { maxHeight: '0px'},
          entering: { maxHeight: '500px'},
          entered: { maxHeight: '500px'},
        }[state],
      }}
    >
      {/* 列表项内容 */}
    </List>
  )}
</Transition>

问题现象

  • 初始设置下:列表展开正常,但折叠时存在明显延迟
  • 添加cubic-bezier(0, 1, 0, 1)到transition属性后:折叠动画恢复正常,但展开动画完全失效

添加cubic-bezier前的效果:
添加cubic-bezier前的折叠延迟效果

添加cubic-bezier后的效果:
添加cubic-bezier后的展开失效效果


问题根源

用固定值max-height: 500px做动画的核心问题:

  1. 折叠延迟:列表实际高度远小于500px,动画会先消耗大量时间在「从500px降到实际高度」的无视觉变化阶段,最后才快速降到0,导致看起来延迟。
  2. 展开失效:cubic-bezier(0,1,0,1)是先极慢后突变的曲线,展开时动画大部分时间都在「从0到实际高度」之前的阶段,视觉上完全看不到变化。

解决办法

方案1:动态计算实际高度(推荐)

直接获取列表展开后的真实高度,用这个值作为动画的目标max-height,彻底避免无效的高度区间:

import { useRef, useState, useEffect } from 'react';
import { Transition } from 'react-transition-group';
import { List } from '@mui/joy';

// 组件内部代码
const listRef = useRef(null);
const [listRealHeight, setListRealHeight] = useState('0px');

// 组件挂载时获取列表实际高度
useEffect(() => {
  if (listRef.current) {
    setListRealHeight(`${listRef.current.scrollHeight}px`);
  }
}, []);

// 渲染部分
<Transition nodeRef={listRef} in={browseOpen} timeout={1000}>
  {(state) => (
    <List
      ref={listRef}
      aria-labelledby="nav-list-browse"
      sx={{
        '& .JoyListItemButton-root': { p: '8px' },
        transition: 'max-height 1000ms ease',
        overflow: 'hidden',
        maxHeight: {
          exiting: '0px',
          exited: '0px',
          entering: listRealHeight,
          entered: listRealHeight,
        }[state],
      }}
    >
      {/* 列表项内容 */}
    </List>
  )}
</Transition>

方案2:针对状态设置不同过渡曲线

如果不想动态计算高度,可以给进入、退出阶段分别配置合适的曲线:

<Transition nodeRef={nodeRef} in={browseOpen} timeout={1000}>
  {(state) => (
    <List
      aria-labelledby="nav-list-browse"
      sx={{
        '& .JoyListItemButton-root': { p: '8px' },
        overflow: 'hidden',
        maxHeight: {
          exiting: '0px',
          exited: '0px',
          entering: '500px',
          entered: '500px',
        }[state],
        // 进入用常规ease曲线,退出用你需要的贝塞尔曲线
        transition: state.includes('enter')
          ? 'max-height 1000ms ease'
          : 'max-height 1000ms cubic-bezier(0, 1, 0, 1)',
      }}
    >
      {/* 列表项内容 */}
    </List>
  )}
</Transition>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 05:43:25