UNPKG

react-stuck

Version:

React component which loosely implements `position: sticky`

117 lines (101 loc) 3.27 kB
/* global window */ 'use strict'; var React = require('react'), ReactDOM = require('react-dom'), PureRenderMixin = require('react-addons-pure-render-mixin'), throttle = require('lodash/function/throttle'), offset = require('dom-helpers/query/offset'), on = require('dom-helpers/events/on'), off = require('dom-helpers/events/off'); module.exports = React.createClass({ displayName: 'Stuck', propTypes: { children: React.PropTypes.any, className: React.PropTypes.string, height: React.PropTypes.number.isRequired, style: React.PropTypes.object, top: React.PropTypes.number }, mixins: [PureRenderMixin], getDefaultProps: function getDefaultProps() { return { top: 0, style: {} }; }, getInitialState: function getInitialState() { this._offsets = {}; return { className: 'stuck-top' }; }, componentDidMount: function componentDidMount() { this._throttledUpdateOffsets = this._getThrottledUpdateOffsets(); on(window, 'scroll', this._updateClass); on(window, 'scroll', this._throttledUpdateOffsets); this.componentWillReceiveProps(this.props); }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { this._updateOffsets(nextProps); this._updateClass(); }, componentWillUnmount: function componentWillUnmount() { off(window, 'scroll', this._updateClass); off(window, 'scroll', this._throttledUpdateOffsets); }, _getThrottledUpdateOffsets: function _getThrottledUpdateOffsets() { function update() { this._updateOffsets(this.props); } return throttle(update.bind(this), 500); }, _updateOffsets: function _updateOffsets(nextProps) { var containerNode = ReactDOM.findDOMNode(this), stuckNode = ReactDOM.findDOMNode(this.refs.stuck), top = offset(containerNode).top, bottom = top + nextProps.height - stuckNode.offsetHeight; this._offsets = { top: top - nextProps.top, bottom: bottom - nextProps.top }; }, _updateClass: function _updateClass() { var pageYOffset = window.pageYOffset, c; if (pageYOffset <= this._offsets.top) c = 'stuck-top';else if (pageYOffset >= this._offsets.bottom) c = 'stuck-bottom';else if (pageYOffset > this._offsets.top) c = 'stuck'; this.setState({ className: c }); }, _getStyleByClass: function _getStyleByClass(className) { switch (className) { case 'stuck': return { position: 'fixed', top: this.props.top }; case 'stuck-bottom': return { position: 'absolute', top: this._offsets.bottom + this.props.top }; default: return { // need 'inline-block' to accurately calculate // the height (with margins) of the `props.children` display: 'inline-block' }; } }, render: function render() { return React.createElement( 'div', { className: this.props.className, style: this.props.style }, React.createElement( 'div', { ref: 'stuck', style: this._getStyleByClass(this.state.className), className: this.state.className }, this.props.children ) ); } });