如何使用ReactCSSTransitionGroup在React中实现元素高度的动画效果?

29

我正在尝试使用ReactCSSTransitionGroup来对元素高度进行动画处理, 这是我希望实现的动画效果:

http://jsfiddle.net/cherrry/hgk4Lme9/

问题在于我并不总是知道元素的高度, 所以我尝试在componentDidMount期间利用scrollHeightclientHeight或类似的方式进行调整,并尝试设置node.style.height或添加样式表规则。

http://jsfiddle.net/cherrry/dz8uod7u/

离开时的动画看起来很好,但当元素进入时,它会闪烁一下,缩放动画看起来奇怪。

这应该是由于询问node.scrollHeight导致立即发生渲染,所以有没有办法在动画开始之前获取相同的信息并注入CSS规则?或者应该换一种思路考虑?

我对使用max-height解决方案并不满意,因为当max-heightheight不接近或较小时,结果的动画速度会非常奇怪,而我的组件的高度确实变化很大。

我可以想象最终的解决方案可能有点混乱, 但我认为将其制作成Mixin,足以在任何地方重复使用它

4个回答

45

我曾经遇到同样的问题,最终编写了一个独立的组件来实现高度动画。

您可以在此处查看演示: https://stanko.github.io/react-animate-height/

它更容易使用,整个库非常小(~200行)。

<AnimateHeight
  duration={ 500 }
  height={ 'auto' }
>
  <h1>Your content goes here</h1>
  <p>Put as many React or HTML components here.</p>
</AnimateHeight>

抱歉进行了一些自我推广,但是如果你有多个需要动画效果的组件,我认为这可以帮助你节省很多时间。

干杯!


1
嗯,这并不是无耻的自我推销,因为你已经解释得很好了。做得好。但要小心回答旧问题。 - Praveen Kumar Purushothaman
1
谢谢,我没意识到这是一个旧问题,我的错! - Stanko
我喜欢使用React动画高度,但如何在宽度上获得相同的效果呢? - maxcodes
当使用React风格的编程时,这种方法不起作用,即<AnimateHeight> { this.state.show ? <div class="div-to-show"></div> : null } </AnimateHeight> - blueprintchris
2
当然不会。使用以下代码: <AnimateHeight height={ this.state.show ? 'auto' : 0 }><div class="div-to-show"></div></AnimateHeight> AnimateHeight 负责可访问性和隐藏内容。 - Stanko
React的CSSTransition组件的开发者应该感到惭愧,因为他们没有像你们一样提供一个易用的API。非常感谢你们。 - messerbill

16

经过一些实验,我使用低级API ReactTransitionGroup 而不是高级的 ReactCSSTransitionGroup 找到了解决方案。

这里是一个有可用解决方案的 JSFiddle: http://jsfiddle.net/cherrry/0wgp34cr/

在动画之前,它做了3件事:

  1. 获取计算高度、填充和边距
  2. 使用 display: none 隐藏元素,并添加 .anim-enter 设置高度为 0
  3. 创建 .anim-enter-active 的 CSS 规则

开始动画时,它做了2件事:

  1. 取消隐藏元素
  2. 添加 .anim-enter-active 开始动画

JSFiddle 中的一些数字和类名是硬编码的,但很容易将 "mixin" 转换成 React 类,以替代 ReactCSSTransitionGroup


2
一个非常简单而好的方法是通过处理scaleY而不是height。
悬停时应设置属性transform:scaleY(1)(原始为transform:scaleY(0)以隐藏它),动画应针对transform属性进行目标设置。

1
如果您不想导入模块或使用jQuery,这里有一个使用React ref的模板(https://reactjs.org/docs/refs-and-the-dom.html)。
基本上,您可以获得它将要增长到的高度,增长到该高度,然后切换回自动。在回程中,切换到它的高度,然后回到0。
class CollapsibleSectionBlock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showContent: false,
            height: "0px",
            myRef: null,
        };
    }

    componentDidUpdate = (prevProps, prevState) => {
        if (prevState.height === "auto" && this.state.height !== "auto") {
            setTimeout(() => this.setState({ height: "0px" }), 1);
        }
    }

    setInnerRef = (ref) => this.setState({ myRef: ref });

    toggleOpenClose = () => this.setState({
        showContent: !this.state.showContent,
        height: this.state.myRef.scrollHeight,
    });

    updateAfterTransition = () => {
        if (this.state.showContent) {
            this.setState({ height: "auto" });
        }
    };

    render() {
        const { title, children } = this.props;
        return (
            <div>
                <h2 onClick={() => this.toggleOpenClose()}>
                    {title}
                </h2>
                <div
                    ref={this.setInnerRef}
                    onTransitionEnd={() => this.updateAfterTransition()}
                    style={{
                        height: this.state.height,
                        overflow: "hidden",
                        transition: "height 250ms linear 0s",
                    }}
                >
                    {children}
                </div>
            </div>
        );
    }
}

如果您的父容器被隐藏或使用display: none => 这将不起作用:this.state.myRef.scrollHeight(它将等于0) - que1326
正确的,当一个对象没有被渲染时,它是没有高度的。 - blindguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接