resize-sensor--react
Version:
Resize Sensor for React
194 lines (178 loc) • 5.86 kB
JSX
/**
* @license MIT
* @copyright Kirill Shestakov 2017
* @see https://github.com/guitarino/resize-sensor--react/
* ----
* Rework of https://github.com/procurios/ResizeSensor
*/
import React from 'react';
import './raf';
var
// this is for ie9
supportsAttachEvent,
// animation start events with varied prefixes
animStart = [
'webkitAnimationStart',
'animationstart',
'oAnimationStart',
'MSAnimationStart'
]
;
try {
supportsAttachEvent = ('attachEvent' in document);
}
catch(probablyDoingSSR) {
supportsAttachEvent = false;
}
// essentially, this is the idea:
//
// we have contraction and expansion triggers,
// each of them have children
//
// for contraction:
// the child is 2x bigger than container,
// and it's always scrolled to the bottom right,
// so, when contracted, the bottom right scroll
// position changes, and the 'scroll' event gets called
//
// for expansion:
// the child is slightly bigger than container,
// and it's always scrolled to the bottom right,
// so, when the container expands, the scrollbar
// disappears and changes the child's scroll position
//
export default class ResizeSensor extends React.Component {
constructor() {
super();
// when invisible, <ResizeSensor/> size is 0x0
this.dimensions = {
width: 0,
height: 0
};
// binding (needed for requestAnimationFrame callback)
this.onElementResize = this.onElementResize.bind(this);
}
// as you can see, there's triggers that "listen" to expansion
// and triggers that "listen" to contraction
render() {
return (
<div className='resize-sensor-react' ref={(e) => {this.self = e}}>
<div className="resize-sensor-react__expand" ref={(e) => {this.expand = e}}>
<div className="resize-sensor-react__expand-child" ref={(e) => {this.expandChild = e}}></div>
</div>
<div className="resize-sensor-react__contract" ref={(e) => {this.contract = e}}>
<div className="resize-sensor-react__contract-child"></div>
</div>
</div>
);
}
// never update element, just render once
shouldComponentUpdate() {
return false;
}
// overriding onResize if props are updated
componentWillReceiveProps(props) {
this.setOnResize(props);
}
// when component is mounted, we just need to attach handlers
// scroll - needed for detecting resize
// animation start - needed detecting visibility (we need to
// trigger initial update once the element becomes visible
// because the size might have changed)
//
// Note: using addEventListener's ability to trigger `handleEvent`
// so that we don't have to deal with binding
componentDidMount() {
this.setOnResize(this.props);
// ie9 only
if (supportsAttachEvent) {
this.self.attachEvent('onresize', this.onElementResize);
}
// other browsers
else {
this.self.addEventListener('scroll', this, true);
for (var i=0; i<animStart.length; i++) {
this.self.addEventListener(animStart[i], this);
}
// Initial value reset of all triggers
this.resetTriggers();
}
}
// When element is unmounted, need to remove all
componentWillUnmount() {
// ie9 only
if (supportsAttachEvent) {
this.self.detachEvent('onresize', this.onElementResize);
}
// other browsers
else {
for (var i=0; i<animStart.length; i++) {
this.self.removeEventListener(animStart[i], this);
}
this.self.removeEventListener('scroll', this, true);
}
}
// if there's no 'onResize' prop, then we'll fall back
// to this onResize, which will do nothing
onResize() { }
setOnResize(props) {
if ('onResize' in props) {
this.onResize = props.onResize;
}
}
// using addEventListener's handleEvent ability
// so that we don't have to deal with binding
handleEvent(e) {
// on scroll, debounce-ish
if (e.type === 'scroll') {
this.resetTriggers();
if (this.resizeRAF) {
window.cancelAnimationFrame(
this.resizeRAF
);
}
this.resizeRAF = window.requestAnimationFrame(
this.onElementResize
);
}
// when element becomes visible, reset the trigger sizes;
// the scroll will be triggered if sizes changed
else {
if (e.animationName === 'resize-sensor-react-animation') {
this.resetTriggers();
}
}
}
// check if actually resized, call the callback
onElementResize() {
var currentDimensions = this.getDimensions();
if (this.isResized(currentDimensions)) {
this.dimensions.width = currentDimensions.width;
this.dimensions.height = currentDimensions.height;
this.onResize(this.dimensions.width, this.dimensions.height);
}
}
// just checking if either dimension changed
isResized(currentDimensions) {
return (
currentDimensions.width !== this.dimensions.width ||
currentDimensions.height !== this.dimensions.height
);
}
// returning current dimensions of the resize sensor
getDimensions() {
return {
width: this.self.offsetWidth,
height: this.self.offsetHeight
};
}
// this implements the idea behind resize sensor
resetTriggers() {
this.contract.scrollLeft = this.contract.scrollWidth;
this.contract.scrollTop = this.contract.scrollHeight;
this.expandChild.style.width = (this.expand.offsetWidth + 1) + 'px';
this.expandChild.style.height = (this.expand.offsetHeight + 1) + 'px';
this.expand.scrollLeft = this.expand.scrollWidth;
this.expand.scrollTop = this.expand.scrollHeight;
}
}