How to animate element height in React with ReactCSSTransitionGroup?
Asked Answered
C

4

29

I'm trying to animate element height with ReactCSSTransitionGroup, so this is what I would want the animation looks like:

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

The problem is that I don't always know the element height, so I tried to hack the scrollHeight, clientHeight or something similar during componentDidMount and try to set node.style.height or add rules to stylesheet

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

Leaving animation looks good, however when element enters, it flash a bit and the scaling animation looks strange

It should be because of asking node.scrollHeight caused the rendering occur immediately, so is there anyway to get the same information and inject css rules before animation start? Or should I think other way round?

I'm not very satisfied with the max-height solution, as the resulting animation speed will be very strange when max-height is not close to or smaller to height, and my components' height do vary a lot.

I could imagine the final solution could be a bit messy, but I think making it into a Mixin will be nice enough to reuse it anywhere

Cenesthesia answered 30/3, 2015 at 1:23 Comment(0)
L
47

I had a same problem and ended up writing a standalone component for animating height.

You can see the demo here: https://stanko.github.io/react-animate-height/

It is much easier to use, and whole library is really small (~200 lines)

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

Sorry for the shameless self promotion, but I think it can save you a lot of time if you have more than one component to animate.

Cheers!

Lovable answered 30/6, 2017 at 14:56 Comment(6)
Uh, this is not shameless self promotion as you have explained it as well. Good one. But be careful answering oooooold questions.Hydrophobia
Thanks, I didn't realize it was an old question, my bad!Lovable
I love react animate height but how can I get the same effect on the width.Auricula
This doesn't work when using react-style programming, i.e. <AnimateHeight> { this.state.show ? <div class="div-to-show"></div> : null } </AnimateHeight>Paediatrician
Of course it doesn't. Use this: <AnimateHeight height={ this.state.show ? 'auto' : 0 }><div class="div-to-show"></div></AnimateHeight> AnimateHeight takes care of accessibility and hides the content.Lovable
The devs of the react CSSTransition component should be ashamed since they were not able to provide me an as easy API as yours. Thank you very much.Sams
C
16

After a bit more experiment, I've come up with a solution by using the low-level API ReactTransitionGroup instead of high-level ReactCSSTransitionGroup

Here's the JSFiddle with a working solution: http://jsfiddle.net/cherrry/0wgp34cr/

Before the animation, it's doing 3 things:

  1. get computed height, paddings and margins
  2. hide the element with display: none and add .anim-enter to set height to 0
  3. create css rule for .anim-enter-active

To start the animation, it's doing 2 things:

  1. unhide the element
  2. add .anim-enter-active to start the animation

Some numbers and class name in JSFiddle were hard-coded, but it should be easy enough to transform the "mixin" into a React class as a replacement of ReactCSSTransitionGroup

Cenesthesia answered 31/3, 2015 at 15:21 Comment(0)
P
2

A very easy and nice approach is by handling scaleY instead of height. The hover should set the attribute transform: scaleY(1) (originally => transform: scaleY(0) to hide it) and the animation should target transform attribute.

Phillips answered 24/8, 2021 at 20:49 Comment(0)
O
1

If you dont want to import a module or use qjuery, here is a template using React ref (https://reactjs.org/docs/refs-and-the-dom.html)

Basically you get the height it will be, grow to that height, switch back to auto. On the way back switch to the height it is, then back to 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>
        );
    }
}
Outspread answered 2/8, 2019 at 21:18 Comment(2)
If you have the parent container hidden / with display: none => this will not work: this.state.myRef.scrollHeight ( it will be equal to 0 )Copaiba
Correct, an object does not have a height when its not rendered.Outspread

© 2022 - 2024 — McMap. All rights reserved.