Last active
April 18, 2018 05:18
-
-
Save JonathonAshworth/5805d8c63be621841514642e8191b8cd to your computer and use it in GitHub Desktop.
Container element that automatically animates height changes when children change size
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { string, object } from 'prop-types' | |
import styles from './AnimatedHeightContainer.css' | |
class AnimatedHeightContainer extends React.Component { | |
static propTypes = { | |
className: string, | |
style: object, | |
} | |
constructor () { | |
super() | |
this.el = null | |
this.manualUpdate = this.manualUpdate.bind(this) | |
} | |
componentDidMount () { | |
this.manualUpdate() | |
window.addEventListener('resize', this.manualUpdate) | |
} | |
componentWillUnmount () { | |
window.removeEventListener('resize', this.manualUpdate) | |
} | |
componentDidUpdate () { | |
this.manualUpdate() | |
} | |
async manualUpdate () { | |
const animationFrame = async () => new Promise(res => requestAnimationFrame(() => res())) | |
// Compute the height of the content | |
const originalHeight = this.el.style.height | |
this.el.style.height = 'auto' | |
const computedHeight = this.el.getBoundingClientRect().height | |
await animationFrame() // [0] | |
this.el.style.height = originalHeight | |
// Manually set the height to the computed height to force a css transition | |
// (this is needed since css won't transition to 'auto') | |
await animationFrame() | |
this.el.style.height = computedHeight + 'px' | |
// [0] If we have multiple AnimatedHeightContainer's nested, the ancestor's height | |
// calculation (above) is dependent on all it's decendant's computed heights | |
// (i.e height: auto) So we need to pause somehow until they've all been | |
// calculated. | |
// All the browsers seem to handle this timing stuff differently. setTimeout | |
// doesn't work half the time on Chrome, but requestAnimationFrame results in | |
// a 1-frame flash of bad animation on Safari. I've picked the lesser of two | |
// evils for now. | |
} | |
render () { | |
return ( | |
<div | |
className={`${styles.container} ${this.props.className}`} | |
style={this.props.style} | |
ref={el => this.el = el} | |
> | |
{this.props.children} | |
</div> | |
) | |
} | |
} | |
export default AnimatedHeightContainer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment