在React环境中使用动画实现折叠动画react-transition-group

345 阅读1分钟

需要实现的效果:

类似antd组件中的折叠面板的折叠动画

遇到的问题:

css原生不支持,从height:0到height:auto的过渡(因为auto是代表一个不确定的值, 过渡动画需要明确的开始值和结束值)

依赖: npm install react-transition-group

实现代码:

import React, { useRef, useState } from 'react';
import { Transition } from 'react-transition-group';

const duration = 300; // 动画持续时间

const defaultStyle = {
  transition: `height ${duration}ms ease-in-out`,
  overflow: 'hidden',
};

const transitionStyles = {
  entering: { height: 0 },
  entered:  { height: 'auto' }, // 动画结束后设置为 auto
  exiting:  { height: 'auto' }, // 开始退出动画之前设置为 auto
  exited:  { height: 0 },
};

const Collapsible = ({ children }) => {
  const [isOpen, setIsOpen] = useState(false);
  const contentRef = useRef(null);

  return (
    <>
      <button onClick={() => setIsOpen(!isOpen)}>
        {isOpen ? 'Collapse' : 'Expand'}
      </button>
      <Transition
        in={isOpen}
        timeout={duration}
        onEntering={node => {
          // 动态获取内容高度并应用,创建展开动画
          node.style.height = `${node.scrollHeight}px`;
        }}
        onEntered={node => {
          // 动画结束后重置高度, 以支持内容动态变化的自适应
          node.style.height = 'auto';
        }}
        onExit={node => {
          // 强制 reflow,获取实际高度值作为最初状态
          node.style.height = `${node.scrollHeight}px`;
        }}
        onExiting={node => {
          // 强制 reflow,设置高度为0开始收缩动画
          node.offsetHeight; // eslint-disable-line no-unused-expressions
          node.style.height = '0';
        }}
      >
        {state => (
          <div
            ref={contentRef}
            style={{
              ...defaultStyle,
              ...transitionStyles[state]
            }}
          >
            {children}
          </div>
        )}
      </Transition>
    </>
  );
};

export default Collapsible;