2015-08-03 16:49:42 +08:00
|
|
|
import React from 'react';
|
2015-10-21 17:59:57 +08:00
|
|
|
import ReactDOM from 'react-dom';
|
2016-06-16 22:34:54 +08:00
|
|
|
import addEventListener from 'rc-util/lib/Dom/addEventListener';
|
2015-11-24 20:03:57 +08:00
|
|
|
import classNames from 'classnames';
|
2016-05-09 21:49:31 +08:00
|
|
|
import warning from 'warning';
|
2016-07-09 16:00:05 +08:00
|
|
|
import shallowequal from 'shallowequal';
|
2015-08-03 16:49:42 +08:00
|
|
|
|
2016-08-18 11:27:37 +08:00
|
|
|
function getScroll(target, top) {
|
|
|
|
const prop = top ? 'pageYOffset' : 'pageXOffset';
|
|
|
|
const method = top ? 'scrollTop' : 'scrollLeft';
|
|
|
|
const isWindow = target === window;
|
|
|
|
|
|
|
|
let ret = isWindow ? target[prop] : target[method];
|
|
|
|
// ie6,7,8 standard mode
|
|
|
|
if (isWindow && typeof ret !== 'number') {
|
|
|
|
ret = window.document.documentElement[method];
|
2015-08-04 14:57:18 +08:00
|
|
|
}
|
2016-08-18 11:27:37 +08:00
|
|
|
|
2015-08-04 14:57:18 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-08-18 11:27:37 +08:00
|
|
|
function getTargetRect(target) {
|
|
|
|
return target !== window ?
|
|
|
|
target.getBoundingClientRect() :
|
|
|
|
{ top: 0, left: 0, bottom: 0 };
|
|
|
|
}
|
|
|
|
|
|
|
|
function getOffset(element, target) {
|
|
|
|
const elemRect = element.getBoundingClientRect();
|
|
|
|
const targetRect = getTargetRect(target);
|
|
|
|
|
|
|
|
const scrollTop = getScroll(target, true);
|
|
|
|
const scrollLeft = getScroll(target, false);
|
|
|
|
|
|
|
|
const docElem = window.document.body;
|
|
|
|
const clientTop = docElem.clientTop || 0;
|
|
|
|
const clientLeft = docElem.clientLeft || 0;
|
2015-08-04 14:57:18 +08:00
|
|
|
|
|
|
|
return {
|
2016-08-18 11:27:37 +08:00
|
|
|
top: elemRect.top - targetRect.top +
|
|
|
|
scrollTop - clientTop,
|
|
|
|
left: elemRect.left - targetRect.left +
|
|
|
|
scrollLeft - clientLeft,
|
2015-08-04 14:57:18 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-03-29 17:33:37 +08:00
|
|
|
export default class Affix extends React.Component {
|
|
|
|
static propTypes = {
|
2016-02-29 16:44:38 +08:00
|
|
|
offsetTop: React.PropTypes.number,
|
|
|
|
offsetBottom: React.PropTypes.number,
|
2016-08-18 11:27:37 +08:00
|
|
|
target: React.PropTypes.func,
|
2016-03-29 17:33:37 +08:00
|
|
|
}
|
2015-09-01 16:18:46 +08:00
|
|
|
|
2016-05-26 16:06:15 +08:00
|
|
|
static defaultProps = {
|
2016-08-18 11:27:37 +08:00
|
|
|
target() {
|
|
|
|
return window;
|
|
|
|
},
|
2016-05-26 16:06:15 +08:00
|
|
|
onChange() {},
|
|
|
|
}
|
|
|
|
|
2016-03-29 17:33:37 +08:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
2016-02-29 12:10:36 +08:00
|
|
|
affixStyle: null,
|
2016-07-11 14:52:21 +08:00
|
|
|
placeholderStyle: null,
|
2015-08-03 16:49:42 +08:00
|
|
|
};
|
2016-03-29 17:33:37 +08:00
|
|
|
}
|
2015-08-03 16:49:42 +08:00
|
|
|
|
2016-07-11 14:52:21 +08:00
|
|
|
setAffixStyle(e, affixStyle) {
|
2016-08-18 11:27:37 +08:00
|
|
|
const { onChange, target } = this.props;
|
2016-07-09 16:00:05 +08:00
|
|
|
const originalAffixStyle = this.state.affixStyle;
|
2016-08-18 11:27:37 +08:00
|
|
|
const isWindow = target() === window;
|
|
|
|
if (e.type === 'scroll' && originalAffixStyle && affixStyle && isWindow) {
|
2016-07-11 14:52:21 +08:00
|
|
|
return;
|
|
|
|
}
|
2016-07-09 16:00:05 +08:00
|
|
|
if (shallowequal(affixStyle, originalAffixStyle)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.setState({ affixStyle }, () => {
|
|
|
|
const affixed = !!this.state.affixStyle;
|
|
|
|
if ((affixStyle && !originalAffixStyle) ||
|
|
|
|
(!affixStyle && originalAffixStyle)) {
|
2016-08-18 11:27:37 +08:00
|
|
|
onChange(affixed);
|
2016-07-09 16:00:05 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-11 14:52:21 +08:00
|
|
|
setPlaceholderStyle(e, placeholderStyle) {
|
|
|
|
const originalPlaceholderStyle = this.state.placeholderStyle;
|
|
|
|
if (e.type === 'resize') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (shallowequal(placeholderStyle, originalPlaceholderStyle)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.setState({ placeholderStyle });
|
|
|
|
}
|
|
|
|
|
2016-08-18 11:27:37 +08:00
|
|
|
updatePosition = (e) => {
|
|
|
|
let { offsetTop, offsetBottom, offset, target } = this.props;
|
|
|
|
const targetNode = target();
|
2016-05-09 21:49:31 +08:00
|
|
|
|
|
|
|
// Backwards support
|
|
|
|
offsetTop = offsetTop || offset;
|
2016-08-18 11:27:37 +08:00
|
|
|
const scrollTop = getScroll(targetNode, true);
|
|
|
|
const elemOffset = getOffset(ReactDOM.findDOMNode(this), targetNode);
|
2016-02-29 16:44:38 +08:00
|
|
|
const elemSize = {
|
2016-08-18 11:27:37 +08:00
|
|
|
width: this.refs.fixedNode.offsetWidth,
|
|
|
|
height: this.refs.fixedNode.offsetHeight,
|
2016-02-29 16:44:38 +08:00
|
|
|
};
|
2015-08-03 16:49:42 +08:00
|
|
|
|
2016-02-29 16:44:38 +08:00
|
|
|
const offsetMode = {};
|
2016-08-18 11:27:37 +08:00
|
|
|
// Default to `offsetTop=0`.
|
2016-02-29 16:44:38 +08:00
|
|
|
if (typeof offsetTop !== 'number' && typeof offsetBottom !== 'number') {
|
|
|
|
offsetMode.top = true;
|
|
|
|
offsetTop = 0;
|
|
|
|
} else {
|
|
|
|
offsetMode.top = typeof offsetTop === 'number';
|
|
|
|
offsetMode.bottom = typeof offsetBottom === 'number';
|
2015-08-03 16:49:42 +08:00
|
|
|
}
|
|
|
|
|
2016-08-18 11:27:37 +08:00
|
|
|
const targetRect = getTargetRect(targetNode);
|
|
|
|
const targetInnerHeight = targetNode.innerHeight || targetNode.clientHeight;
|
2016-02-29 16:44:38 +08:00
|
|
|
if (scrollTop > elemOffset.top - offsetTop && offsetMode.top) {
|
|
|
|
// Fixed Top
|
2016-07-11 14:52:21 +08:00
|
|
|
this.setAffixStyle(e, {
|
2016-07-09 16:00:05 +08:00
|
|
|
position: 'fixed',
|
2016-08-18 11:27:37 +08:00
|
|
|
top: targetRect.top + offsetTop,
|
|
|
|
left: targetRect.left + elemOffset.left,
|
2016-07-09 16:00:05 +08:00
|
|
|
width: ReactDOM.findDOMNode(this).offsetWidth,
|
|
|
|
});
|
2016-07-11 14:52:21 +08:00
|
|
|
this.setPlaceholderStyle(e, {
|
|
|
|
width: ReactDOM.findDOMNode(this).offsetWidth,
|
|
|
|
height: ReactDOM.findDOMNode(this).offsetHeight,
|
|
|
|
});
|
2016-08-18 11:27:37 +08:00
|
|
|
} else if (
|
|
|
|
scrollTop < elemOffset.top + elemSize.height + offsetBottom - targetInnerHeight &&
|
|
|
|
offsetMode.bottom
|
|
|
|
) {
|
2016-02-29 16:44:38 +08:00
|
|
|
// Fixed Bottom
|
2016-08-18 11:27:37 +08:00
|
|
|
const targetBottomOffet = targetNode === window ? 0 : (window.innerHeight - targetRect.bottom);
|
2016-07-11 14:52:21 +08:00
|
|
|
this.setAffixStyle(e, {
|
2016-07-09 16:00:05 +08:00
|
|
|
position: 'fixed',
|
2016-08-18 11:27:37 +08:00
|
|
|
bottom: targetBottomOffet + offsetBottom,
|
|
|
|
left: targetRect.left + elemOffset.left,
|
2016-07-09 16:00:05 +08:00
|
|
|
width: ReactDOM.findDOMNode(this).offsetWidth,
|
|
|
|
});
|
2016-07-11 14:52:21 +08:00
|
|
|
this.setPlaceholderStyle(e, {
|
|
|
|
width: ReactDOM.findDOMNode(this).offsetWidth,
|
|
|
|
height: ReactDOM.findDOMNode(this).offsetHeight,
|
|
|
|
});
|
2016-07-09 16:00:05 +08:00
|
|
|
} else {
|
2016-07-11 14:52:21 +08:00
|
|
|
this.setAffixStyle(e, null);
|
|
|
|
this.setPlaceholderStyle(e, null);
|
2015-08-03 16:49:42 +08:00
|
|
|
}
|
2016-03-29 17:33:37 +08:00
|
|
|
}
|
2015-08-03 16:49:42 +08:00
|
|
|
|
|
|
|
componentDidMount() {
|
2016-05-09 21:49:31 +08:00
|
|
|
warning(!('offset' in this.props), '`offset` prop of Affix is deprecated, use `offsetTop` instead.');
|
2016-08-18 11:27:37 +08:00
|
|
|
|
|
|
|
const target = this.props.target;
|
|
|
|
this.setTargetEventListeners(target);
|
2016-03-29 17:33:37 +08:00
|
|
|
}
|
2015-08-03 16:49:42 +08:00
|
|
|
|
2016-08-18 11:27:37 +08:00
|
|
|
componentWillReceiveProps(nextProps) {
|
|
|
|
if (this.props.target !== nextProps.target) {
|
|
|
|
this.clearScrollEventListeners();
|
|
|
|
this.setTargetEventListeners(nextProps.target);
|
|
|
|
|
|
|
|
// Mock Event object.
|
|
|
|
this.updatePosition({});
|
2015-08-04 15:03:00 +08:00
|
|
|
}
|
2016-03-29 17:33:37 +08:00
|
|
|
}
|
2015-08-03 16:49:42 +08:00
|
|
|
|
2016-08-18 11:27:37 +08:00
|
|
|
componentWillUnmount() {
|
|
|
|
this.clearScrollEventListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
setTargetEventListeners(getTarget) {
|
|
|
|
const target = getTarget();
|
|
|
|
this.scrollEvent = addEventListener(target, 'scroll', this.updatePosition);
|
|
|
|
this.resizeEvent = addEventListener(target, 'resize', this.updatePosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
clearScrollEventListeners() {
|
|
|
|
['scrollEvent', 'resizeEvent'].forEach((name) => {
|
|
|
|
if (this[name]) {
|
|
|
|
this[name].remove();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-08-03 16:49:42 +08:00
|
|
|
render() {
|
2015-11-24 20:03:57 +08:00
|
|
|
const className = classNames({
|
2016-02-29 16:44:38 +08:00
|
|
|
'ant-affix': this.state.affixStyle,
|
2015-10-09 10:55:52 +08:00
|
|
|
});
|
2015-08-03 16:49:42 +08:00
|
|
|
|
2016-07-04 12:13:05 +08:00
|
|
|
const props = { ...this.props };
|
|
|
|
delete props.offsetTop;
|
|
|
|
delete props.offsetBottom;
|
2016-08-18 11:27:37 +08:00
|
|
|
delete props.target;
|
2016-07-04 12:13:05 +08:00
|
|
|
|
2015-08-03 16:49:42 +08:00
|
|
|
return (
|
2016-07-11 14:52:21 +08:00
|
|
|
<div {...props} style={this.state.placeholderStyle}>
|
2016-02-29 16:44:38 +08:00
|
|
|
<div className={className} ref="fixedNode" style={this.state.affixStyle}>
|
2015-08-04 14:57:18 +08:00
|
|
|
{this.props.children}
|
|
|
|
</div>
|
2015-08-03 16:49:42 +08:00
|
|
|
</div>
|
|
|
|
);
|
2016-03-29 17:33:37 +08:00
|
|
|
}
|
|
|
|
}
|