@alifd/next
Version:
A configurable component library for web built on React.
342 lines (290 loc) • 12.1 kB
JavaScript
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
var _class, _temp;
import { dom } from '../../util';
var VIEWPORT = 'viewport';
// IE8 not support pageXOffset
var getPageX = function getPageX() {
return window.pageXOffset || document.documentElement.scrollLeft;
};
var getPageY = function getPageY() {
return window.pageYOffset || document.documentElement.scrollTop;
};
/**
* @private get element rect
* @param {Element} elem
* @return {Object}
*/
function _getElementRect(elem) {
var offsetTop = 0,
offsetLeft = 0,
scrollTop = 0,
scrollLeft = 0;
var offsetHeight = elem.offsetHeight;
var offsetWidth = elem.offsetWidth;
do {
if (!isNaN(elem.offsetTop)) {
offsetTop += elem.offsetTop;
}
if (!isNaN(elem.offsetLeft)) {
offsetLeft += elem.offsetLeft;
}
if (!isNaN(elem.scrollTop)) {
scrollTop += elem.scrollTop;
}
if (!isNaN(elem.scrollLeft)) {
scrollLeft += elem.scrollLeft;
}
} while ((elem = elem.offsetParent) !== null);
return {
top: offsetTop - scrollTop - (document.documentElement.scrollTop || document.body.scrollTop),
left: offsetLeft - scrollLeft - (document.documentElement.scrollLeft || document.body.scrollLeft),
height: offsetHeight,
width: offsetWidth
};
}
/**
* @private get viewport size
* @return {Object}
*/
function _getViewportSize() {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
}
var Position = (_temp = _class = function () {
function Position(props) {
_classCallCheck(this, Position);
this.pinElement = props.pinElement;
this.baseElement = props.baseElement;
this.align = props.align || 'tl tl';
this.offset = props.offset || [0, 0];
this.needAdjust = props.needAdjust || false;
this.isRtl = props.isRtl || false;
}
/**
* @public static place method
* @param {Object} props
* @param {DOM} props.pinElement
* @param {DOM} props.baseElement
* @param {String} props.align
* @param {Number} props.offset
* @param {Boolean} props.needAdjust
* @param {Boolean} props.isRtl
* @return {Position}
*/
Position.prototype.setPosition = function setPosition() {
var pinElement = this.pinElement;
var baseElement = this.baseElement;
var expectedAlign = this._getExpectedAlign();
var isPinFixed = void 0,
isBaseFixed = void 0,
firstPositionResult = void 0;
if (pinElement === VIEWPORT) {
return;
}
if (dom.getStyle(pinElement, 'position') !== 'fixed') {
dom.setStyle(pinElement, 'position', 'absolute');
isPinFixed = false;
} else {
isPinFixed = true;
}
if (baseElement === VIEWPORT || dom.getStyle(baseElement, 'position') !== 'fixed') {
isBaseFixed = false;
} else {
isBaseFixed = true;
}
// 根据期望的定位
for (var i = 0; i < expectedAlign.length; i++) {
var align = expectedAlign[i];
var pinElementPoints = this._normalizePosition(pinElement, align.split(' ')[0], isPinFixed);
var baseElementPoints = this._normalizePosition(baseElement, align.split(' ')[1], isPinFixed);
var pinElementParentOffset = this._getParentOffset(pinElement);
var baseElementOffset = isPinFixed && isBaseFixed ? this._getLeftTop(baseElement) : baseElementPoints.offset();
var top = baseElementOffset.top + baseElementPoints.y - pinElementParentOffset.top - pinElementPoints.y;
var left = baseElementOffset.left + baseElementPoints.x - pinElementParentOffset.left - pinElementPoints.x;
this._setPinElementPostion(pinElement, { left: left, top: top }, this.offset);
if (!firstPositionResult) {
firstPositionResult = { left: left, top: top };
}
if (this._isInViewport(pinElement)) {
return align;
}
}
// This will only execute if `pinElement` could not be placed entirely in the Viewport
var inViewportLeft = this._makeElementInViewport(pinElement, firstPositionResult.left, 'Left', isPinFixed);
var inViewportTop = this._makeElementInViewport(pinElement, firstPositionResult.top, 'Top', isPinFixed);
this._setPinElementPostion(pinElement, { left: inViewportLeft, top: inViewportTop }, this.offset);
return expectedAlign[0];
};
Position.prototype._getParentOffset = function _getParentOffset(element) {
var parent = element.offsetParent || document.documentElement;
var offset = void 0;
if (parent === document.body && dom.getStyle(parent, 'position') === 'static') {
offset = {
top: 0,
left: 0
};
} else {
offset = this._getElementOffset(parent);
}
offset.top += parseFloat(dom.getStyle(parent, 'border-top-width'), 10);
offset.left += parseFloat(dom.getStyle(parent, 'border-left-width'), 10);
offset.offsetParent = parent;
return offset;
};
Position.prototype._makeElementInViewport = function _makeElementInViewport(pinElement, number, type, isPinFixed) {
var result = number;
var docElement = document.documentElement;
var offsetParent = pinElement.offsetParent || document.documentElement;
if (result < 0) {
if (isPinFixed) {
result = 0;
} else if (offsetParent === document.body && dom.getStyle(offsetParent, 'position') === 'static') {
// Only when div's offsetParent is document.body, we set new position result.
result = Math.max(docElement['scroll' + type], document.body['scroll' + type]);
}
}
return result;
};
Position.prototype._normalizePosition = function _normalizePosition(element, align, isPinFixed) {
var points = this._normalizeElement(element, isPinFixed);
this._normalizeXY(points, align);
return points;
};
Position.prototype._normalizeXY = function _normalizeXY(points, align) {
var x = align.split('')[1];
var y = align.split('')[0];
points.x = this._xyConverter(x, points, 'width');
points.y = this._xyConverter(y, points, 'height');
return points;
};
Position.prototype._xyConverter = function _xyConverter(align, points, type) {
var res = align.replace(/t|l/gi, '0%').replace(/c/gi, '50%').replace(/b|r/gi, '100%').replace(/(\d+)%/gi, function (m, d) {
return points.size()[type] * (d / 100);
});
return parseFloat(res, 10) || 0;
};
Position.prototype._getLeftTop = function _getLeftTop(element) {
return {
left: parseFloat(dom.getStyle(element, 'left')) || 0,
top: parseFloat(dom.getStyle(element, 'top')) || 0
};
};
Position.prototype._normalizeElement = function _normalizeElement(element, isPinFixed) {
var _this = this;
var result = {
element: element,
x: 0,
y: 0
},
isViewport = element === VIEWPORT,
docElement = document.documentElement;
result.offset = function () {
if (isPinFixed) {
return {
left: 0,
top: 0
};
} else if (isViewport) {
return {
left: getPageX(),
top: getPageY()
};
} else {
return _this._getElementOffset(element);
}
};
result.size = function () {
if (isViewport) {
return {
width: docElement.clientWidth,
height: docElement.clientHeight
};
} else {
return {
width: element.offsetWidth,
height: element.offsetHeight
};
}
};
return result;
};
Position.prototype._getElementOffset = function _getElementOffset(element) {
var rect = element.getBoundingClientRect();
var docElement = document.documentElement;
var body = document.body;
var docClientLeft = docElement.clientLeft || body.clientLeft || 0;
var docClientTop = docElement.clientTop || body.clientTop || 0;
return {
left: rect.left + (getPageX() - docClientLeft),
top: rect.top + (getPageY() - docClientTop)
};
};
// According to the location of the overflow to calculate the desired positioning
Position.prototype._getExpectedAlign = function _getExpectedAlign() {
var align = this.isRtl ? this._replaceAlignDir(this.align, /l|r/g, { l: 'r', r: 'l' }) : this.align;
var expectedAlign = [align];
if (this.needAdjust) {
if (/t|b/g.test(align)) {
expectedAlign.push(this._replaceAlignDir(align, /t|b/g, { t: 'b', b: 't' }));
}
if (/l|r/g.test(align)) {
expectedAlign.push(this._replaceAlignDir(align, /l|r/g, { l: 'r', r: 'l' }));
}
if (/c/g.test(align)) {
expectedAlign.push(this._replaceAlignDir(align, /c(?= |$)/g, { c: 'l' }));
expectedAlign.push(this._replaceAlignDir(align, /c(?= |$)/g, { c: 'r' }));
}
expectedAlign.push(this._replaceAlignDir(align, /l|r|t|b/g, {
l: 'r',
r: 'l',
t: 'b',
b: 't'
}));
}
return expectedAlign;
};
// Transform align order.
Position.prototype._replaceAlignDir = function _replaceAlignDir(align, regExp, map) {
return align.replace(regExp, function (res) {
return map[res];
});
};
// Detecting element is in the window, we want to adjust position later.
Position.prototype._isInViewport = function _isInViewport(element) {
var viewportSize = _getViewportSize();
// Avoid animate problem that use offsetWidth instead of getBoundingClientRect.
var elementRect = _getElementRect(element);
return elementRect.left >= 0 && elementRect.left + element.offsetWidth <= viewportSize.width && elementRect.top >= 0 && elementRect.top + element.offsetHeight <= viewportSize.height;
};
// 在这里做RTL判断 top-left 定位转化为等效的 top-right定位
Position.prototype._setPinElementPostion = function _setPinElementPostion(pinElement, postion) {
var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0];
var top = postion.top,
left = postion.left;
if (!this.isRtl) {
dom.setStyle(pinElement, {
left: left + offset[0] + 'px',
top: top + offset[1] + 'px'
});
return;
}
// transfer {left,top} equaly to {right,top}
var pinElementParentOffset = this._getParentOffset(pinElement);
var _getElementRect2 = _getElementRect(pinElementParentOffset.offsetParent),
offsetParentWidth = _getElementRect2.width;
var _getElementRect3 = _getElementRect(pinElement),
width = _getElementRect3.width;
var right = offsetParentWidth - (left + width);
dom.setStyle(pinElement, {
left: 'auto',
right: right + offset[0] + 'px',
top: top + offset[1] + 'px'
});
};
return Position;
}(), _class.VIEWPORT = VIEWPORT, _class.place = function (props) {
return new Position(props).setPosition();
}, _temp);
export { Position as default };