React列表项位置平滑过渡:关闭展开框避免跳动
Got it, let's fix that jarring jump when collapsing your expandable list items! The issue with your current setup is that when expanded flips to false, React yanks the entire expandable div out of the DOM instantly—no time for the browser to animate a smooth transition. Here's how to fix this step by step:
Why your current approach doesn't work
Using {this.state.expanded && <div>Expandable</div>} means the element is either fully present or completely gone. CSS transitions can't animate "nothing to something" (or vice versa) because there's no element to apply the animation to. We need to keep the element in the DOM and animate its height instead.
Solution 1: For fixed-height expandable content
If your expandable section has a set height, this is super straightforward. Ditch the conditional rendering and use CSS transitions on height:
class ListItem extends React.Component { constructor(props) { super(props); this.state = { expanded: false }; } toggleExpand = () => { this.setState(prev => ({ expanded: !prev.expanded })); }; render() { return ( <div className="list-item-container"> <button onClick={this.toggleExpand}> {this.state.expanded ? 'Collapse' : 'Expand'} </button> {/* Always render the expandable div, animate height */} <div className="expandable-section" style={{ height: this.state.expanded ? '180px' : '0', overflow: 'hidden', transition: 'height 0.3s ease-in-out', background: '#f5f5f5', padding: this.state.expanded ? '16px' : '0 16px' }} > <p>Your fixed-height expandable content here</p> <p>More fixed content...</p> </div> </div> ); } }
Solution 2: For dynamic-height content (the flexible approach)
If your expandable content has variable height (like dynamic text, images, or components), we need to grab its natural height using a React ref. This way the animation will perfectly match the actual content size:
class DynamicListItem extends React.Component { constructor(props) { super(props); this.state = { expanded: false, contentHeight: 0 // Stores the natural height of the expandable content }; this.expandableRef = React.createRef(); } toggleExpand = () => { if (this.state.expanded) { // Collapse sequence: first set height to current scroll height, then animate to 0 this.setState( { contentHeight: this.expandableRef.current.scrollHeight }, () => { setTimeout(() => { this.setState({ contentHeight: 0, expanded: false }); }, 10); // Tiny delay to let CSS register the height change } ); } else { // Expand sequence: first show the content, then set height to its natural size this.setState({ expanded: true }, () => { this.setState({ contentHeight: this.expandableRef.current.scrollHeight }); }); } }; // Optional: Update height if content changes while expanded componentDidUpdate(prevProps) { if (prevProps.content !== this.props.content && this.state.expanded) { this.setState({ contentHeight: this.expandableRef.current.scrollHeight }); } } render() { const { expanded, contentHeight } = this.state; return ( <div className="list-item-container"> <button onClick={this.toggleExpand}> {expanded ? 'Collapse' : 'Expand'} </button> <div ref={this.expandableRef} className="expandable-section" style={{ height: `${contentHeight}px`, overflow: 'hidden', transition: 'height 0.3s ease-in-out', background: '#f5f5f5', padding: expanded ? '16px' : '0 16px' }} > {/* Your dynamic content goes here */} {this.props.content} </div> </div> ); } }
Key tips to make this work
- Never use
display: nonefor the expandable element—this removes it from the layout entirely, so no animation can happen. Stick toheight: 0+overflow: hidden. - The tiny
setTimeoutin the collapse sequence is crucial: it gives the browser a moment to register the full height value before starting the transition to 0. Without it, the animation might skip. - If you want to animate other properties (like padding or opacity), add them to the
transitionproperty (e.g.,transition: height 0.3s, padding 0.3s ease-in-out).
This approach will make your list items glide smoothly into place when expanding/collapsing, no more sudden jumps!
内容的提问来源于stack exchange,提问作者Björn Rave

