UNPKG

11.4 kBJavaScriptView Raw
1import _extends from 'babel-runtime/helpers/extends';
2import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
3import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
4import _inherits from 'babel-runtime/helpers/inherits';
5
6var _class, _temp;
7
8import React from 'react';
9import PropTypes from 'prop-types';
10import classnames from 'classnames';
11import { findDOMNode } from 'react-dom';
12import { polyfill } from 'react-lifecycles-compat';
13import ResizeObserver from 'resize-observer-polyfill';
14
15import { obj, events, func } from '../util';
16import ConfigProvider from '../config-provider';
17import { getScroll, getRect, getNodeHeight } from './util';
18
19/** Affix */
20var Affix = (_temp = _class = function (_React$Component) {
21 _inherits(Affix, _React$Component);
22
23 Affix._getAffixMode = function _getAffixMode(nextProps) {
24 var affixMode = {
25 top: false,
26 bottom: false,
27 offset: 0
28 };
29 if (!nextProps) {
30 return affixMode;
31 }
32 var offsetTop = nextProps.offsetTop,
33 offsetBottom = nextProps.offsetBottom;
34
35
36 if (typeof offsetTop !== 'number' && typeof offsetBottom !== 'number') {
37 // set default
38 affixMode.top = true;
39 } else if (typeof offsetTop === 'number') {
40 affixMode.top = true;
41 affixMode.bottom = false;
42 affixMode.offset = offsetTop;
43 } else if (typeof offsetBottom === 'number') {
44 affixMode.bottom = true;
45 affixMode.top = false;
46 affixMode.offset = offsetBottom;
47 }
48
49 return affixMode;
50 };
51
52 function Affix(props, context) {
53 _classCallCheck(this, Affix);
54
55 var _this = _possibleConstructorReturn(this, _React$Component.call(this, props, context));
56
57 _this._clearContainerEvent = function () {
58 if (_this.timeout) {
59 clearTimeout(_this.timeout);
60 _this.timeout = null;
61 }
62 var container = _this.props.container;
63
64 _this._removeEventHandlerForContainer(container);
65 };
66
67 _this.updatePosition = function () {
68 _this._updateNodePosition();
69 };
70
71 _this._updateNodePosition = function () {
72 var affixMode = _this.state.affixMode;
73 var _this$props = _this.props,
74 container = _this$props.container,
75 useAbsolute = _this$props.useAbsolute;
76
77 var affixContainer = container();
78
79 if (!affixContainer || !_this.affixNode) {
80 return false;
81 }
82 var containerScrollTop = getScroll(affixContainer, true); // 容器在垂直位置上的滚动 offset
83 var affixOffset = _this._getOffset(_this.affixNode, affixContainer); // 目标节点当前相对于容器的 offset
84 var containerHeight = getNodeHeight(affixContainer); // 容器的高度
85 var affixHeight = _this.affixNode.offsetHeight;
86 var containerRect = getRect(affixContainer);
87
88 var affixChildHeight = _this.affixChildNode.offsetHeight;
89
90 var affixStyle = {
91 width: affixOffset.width
92 };
93 var containerStyle = {
94 width: affixOffset.width,
95 height: affixChildHeight
96 };
97 var positionStyle = null;
98 if (affixMode.top && containerScrollTop > affixOffset.top - affixMode.offset) {
99 // affix top
100 if (useAbsolute) {
101 affixStyle.position = 'absolute';
102 affixStyle.top = containerScrollTop - (affixOffset.top - affixMode.offset);
103 positionStyle = 'relative';
104 } else {
105 affixStyle.position = 'fixed';
106 affixStyle.top = affixMode.offset + containerRect.top;
107 }
108 _this._setAffixStyle(affixStyle, true);
109 _this._setContainerStyle(containerStyle);
110 } else if (affixMode.bottom && containerScrollTop < affixOffset.top + affixHeight + affixMode.offset - containerHeight) {
111 // affix bottom
112 affixStyle.height = affixHeight;
113 if (useAbsolute) {
114 affixStyle.position = 'absolute';
115 affixStyle.top = containerScrollTop - (affixOffset.top + affixHeight + affixMode.offset - containerHeight);
116 positionStyle = 'relative';
117 } else {
118 affixStyle.position = 'fixed';
119 affixStyle.bottom = affixMode.offset;
120 }
121 _this._setAffixStyle(affixStyle, true);
122 _this._setContainerStyle(containerStyle);
123 } else {
124 _this._setAffixStyle(null);
125 _this._setContainerStyle(null);
126 }
127
128 if (_this.state.positionStyle !== positionStyle) {
129 _this.setState({ positionStyle: positionStyle });
130 }
131 };
132
133 _this._affixNodeRefHandler = function (ref) {
134 _this.affixNode = ref;
135 };
136
137 _this._affixChildNodeRefHandler = function (ref) {
138 _this.affixChildNode = ref;
139 };
140
141 _this.state = {
142 style: null,
143 containerStyle: null,
144 positionStyle: null,
145 affixMode: Affix._getAffixMode(props)
146 };
147 _this.resizeObserver = new ResizeObserver(_this._updateNodePosition);
148 return _this;
149 }
150
151 Affix.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
152 if ('offsetTop' in nextProps || 'offsetBottom' in nextProps) {
153 return {
154 affixMode: Affix._getAffixMode(nextProps)
155 };
156 }
157 return null;
158 };
159
160 Affix.prototype.componentDidMount = function componentDidMount() {
161 var _this2 = this;
162
163 var container = this.props.container;
164 // wait for parent rendered
165
166 this.timeout = setTimeout(function () {
167 _this2._updateNodePosition();
168 _this2._setEventHandlerForContainer(container);
169 });
170 };
171
172 Affix.prototype.componentDidUpdate = function componentDidUpdate(prevProps, prevState, snapshot) {
173 var _this3 = this;
174
175 if (prevProps.container() !== this.props.container()) {
176 this._clearContainerEvent();
177
178 this.timeout = setTimeout(function () {
179 _this3._setEventHandlerForContainer(_this3.props.container);
180 });
181 }
182
183 setTimeout(this._updateNodePosition);
184 };
185
186 Affix.prototype.componentWillUnmount = function componentWillUnmount() {
187 this._clearContainerEvent();
188 };
189
190 Affix.prototype._setEventHandlerForContainer = function _setEventHandlerForContainer(getContainer) {
191 var container = getContainer();
192 if (!container) {
193 return;
194 }
195 events.on(container, 'scroll', this._updateNodePosition, false);
196 this.resizeObserver.observe(this.affixNode);
197 };
198
199 Affix.prototype._removeEventHandlerForContainer = function _removeEventHandlerForContainer(getContainer) {
200 var container = getContainer();
201 if (container) {
202 events.off(container, 'scroll', this._updateNodePosition);
203 this.resizeObserver.disconnect();
204 }
205 };
206
207 Affix.prototype._setAffixStyle = function _setAffixStyle(affixStyle) {
208 var affixed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
209
210 if (obj.shallowEqual(affixStyle, this.state.style)) {
211 return;
212 }
213
214 this.setState({
215 style: affixStyle
216 });
217
218 var onAffix = this.props.onAffix;
219
220
221 if (affixed) {
222 setTimeout(function () {
223 return onAffix(true);
224 });
225 } else if (!affixStyle) {
226 setTimeout(function () {
227 return onAffix(false);
228 });
229 }
230 };
231
232 Affix.prototype._setContainerStyle = function _setContainerStyle(containerStyle) {
233 if (obj.shallowEqual(containerStyle, this.state.containerStyle)) {
234 return;
235 }
236 this.setState({ containerStyle: containerStyle });
237 };
238
239 Affix.prototype._getOffset = function _getOffset(affixNode, affixContainer) {
240 var affixRect = affixNode.getBoundingClientRect(); // affix 元素 相对浏览器窗口的位置
241 var containerRect = getRect(affixContainer); // affix 容器 相对浏览器窗口的位置
242 var containerScrollTop = getScroll(affixContainer, true);
243 var containerScrollLeft = getScroll(affixContainer, false);
244
245 return {
246 top: affixRect.top - containerRect.top + containerScrollTop,
247 left: affixRect.left - containerRect.left + containerScrollLeft,
248 width: affixRect.width,
249 height: affixRect.height
250 };
251 };
252
253 Affix.prototype.render = function render() {
254 var _classnames;
255
256 var _state = this.state,
257 affixMode = _state.affixMode,
258 positionStyle = _state.positionStyle;
259 var _props = this.props,
260 prefix = _props.prefix,
261 className = _props.className,
262 style = _props.style,
263 children = _props.children;
264
265 var state = this.state;
266 var classNames = classnames((_classnames = {}, _classnames[prefix + 'affix'] = state.style, _classnames[prefix + 'affix-top'] = !state.style && affixMode.top, _classnames[prefix + 'affix-bottom'] = !state.style && affixMode.bottom, _classnames[className] = className, _classnames));
267 var wrapperStyle = _extends({}, style, { position: positionStyle });
268
269 return React.createElement(
270 'div',
271 { ref: this._affixNodeRefHandler, style: wrapperStyle },
272 state.style && React.createElement('div', { style: state.containerStyle, 'aria-hidden': 'true' }),
273 React.createElement(
274 'div',
275 { ref: this._affixChildNodeRefHandler, className: classNames, style: state.style },
276 children
277 )
278 );
279 };
280
281 return Affix;
282}(React.Component), _class.propTypes = {
283 prefix: PropTypes.string,
284 /**
285 * 设置 Affix 需要监听滚动事件的容器元素
286 * @return {ReactElement} 目标容器元素的实例
287 */
288 container: PropTypes.func,
289 /**
290 * 距离窗口顶部达到指定偏移量后触发
291 */
292 offsetTop: PropTypes.number,
293 /**
294 * 距离窗口底部达到制定偏移量后触发
295 */
296 offsetBottom: PropTypes.number,
297 /**
298 * 当元素的样式发生固钉样式变化时触发的回调函数
299 * @param {Boolean} affixed 元素是否被固钉
300 */
301 onAffix: PropTypes.func,
302 /**
303 * 是否启用绝对布局实现 affix
304 * @param {Boolean} 是否启用绝对布局
305 */
306 useAbsolute: PropTypes.bool,
307 className: PropTypes.string,
308 style: PropTypes.object,
309 children: PropTypes.any
310}, _class.defaultProps = {
311 prefix: 'next-',
312 container: function container() {
313 return window;
314 },
315 onAffix: func.noop
316}, _temp);
317Affix.displayName = 'Affix';
318
319
320export default ConfigProvider.config(polyfill(Affix));
\No newline at end of file