Last active
May 2, 2017 09:34
-
-
Save mdarse/e1a2e791886bde678713f1ab1115a2c6 to your computer and use it in GitHub Desktop.
Scaler component to display some inner tree like a full page preview would do (including media queries).
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 { Component, PropTypes } from 'react'; | |
import stylePropType from 'react-style-proptype'; | |
// TODO For SSR support, use this: https://github.com/bvaughn/react-virtualized/blob/78910b626b3c0d31d2a791a7a3f1c0dcaa56a25e/source/vendor/detectElementResize.js | |
// TODO Debounce window resize handler | |
function resetMeasurements() { | |
return { | |
windowWidth: document.documentElement.offsetWidth, | |
scalerWidth: null, | |
contentHeight: null, | |
}; | |
} | |
function px(value) { | |
return `${value}px`; | |
} | |
function outerDivStyle(contentHeight, scale) { | |
const style = { overflow: 'hidden' }; | |
if (contentHeight !== null && scale !== null) { | |
// third render (and each time after second render step) | |
style.height = px(contentHeight * scale); | |
} | |
return style; | |
} | |
function innerDivStyle(windowWidth, scale) { | |
return { | |
width: px(windowWidth), | |
transform: `scale(${scale})`, | |
transformOrigin: 'top left', | |
}; | |
} | |
export default class Scaler extends Component { | |
static propTypes = { | |
children: PropTypes.node.isRequired, | |
style: stylePropType, | |
}; | |
static defaultProps = { | |
style: {}, | |
}; | |
constructor(props) { | |
super(props); | |
this.state = resetMeasurements(); | |
this.lastScale = null; | |
this.handleResize = this.handleResize.bind(this); | |
this.outerDiv = null; | |
this.innerDiv = null; | |
} | |
componentDidMount() { | |
window.addEventListener('resize', this.handleResize); | |
this.updateScalerWidth(); | |
} | |
componentWillReceiveProps(nextProps) { | |
if (this.props.children !== nextProps.children) { | |
this.setState({ contentHeight: null }); | |
} | |
} | |
componentDidUpdate() { | |
if (this.state.scalerWidth === null) { | |
this.updateScalerWidth(); | |
} | |
if (this.state.contentHeight === null && this.innerDiv !== null) { | |
// eslint-disable-next-line react/no-did-update-set-state | |
this.setState({ contentHeight: this.innerDiv.offsetHeight }); | |
} | |
} | |
componentWillUnmount() { | |
window.removeEventListener('resize', this.handleResize); | |
} | |
handleResize() { | |
this.setState(resetMeasurements); | |
} | |
updateScalerWidth() { | |
this.setState({ scalerWidth: this.outerDiv.offsetWidth }); | |
} | |
render() { | |
const { children, style } = this.props; | |
const { windowWidth, scalerWidth, contentHeight } = this.state; | |
// We are doing here a three-step render here: | |
// - *first* without content since we don't know the scale until we measure width of the | |
// outer div element (no explicit height on the outer div element yet) | |
// - a *second time* with content since we know the scale from outer div width (no explicit | |
// height on the outer div element neither) | |
// - and a *third time* with an explicit height set on the outer element. This height is set | |
// according to the measurment we made on the inner div element, after the above render. | |
// | |
// The first render step is only done once after mount, each measurement reset (after | |
// resize, or children change) begins at second render step. In that case, the last known | |
// rendered scale is used (to prevent the blink we would have on first step). | |
// | |
// We assume here that the height set on the third render doesn't impact the height of the | |
// inner div element. This should be guaranteed by the "overflow: hidden" herebelow. We also | |
// assume that the content doesn't impact the width of the outer div element. | |
if (scalerWidth !== null) { | |
// second render (and after each measurement reset) | |
this.lastScale = scalerWidth / windowWidth; | |
} | |
const scale = this.lastScale; | |
const outerStyle = { | |
...style, | |
...outerDivStyle(contentHeight, scale), | |
}; | |
return ( | |
<div ref={(ref) => { this.outerDiv = ref; }} style={outerStyle}> | |
{scale !== null && | |
<div | |
ref={(ref) => { this.innerDiv = ref; }} | |
style={innerDivStyle(windowWidth, scale)} | |
> | |
{children} | |
</div> | |
} | |
</div> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment