解決 body 設(shè)置 zoom 導(dǎo)致各種 popover 錯位的問題(Vue 2 + ElementUI 2)
不適用zoom的話 可以使用ElementUI和autofit.js
import Popper from "element-ui/lib/utils/popper";
const root = window;
// 重寫此方法,以解決 body zoom 導(dǎo)致 ElementUI 所有 popper 相關(guān)元素錯位的問題
// bug 產(chǎn)生原因:因?yàn)榇朔椒ㄒ蕾?`document.documentElement.clientWidth` 和 `document.documentElement.clientHeight`
// 而這兩個值是 body 縮放之前的寬度和高度,換句話說,這兩個值與 `zoom` 無關(guān)
// 所以解決方案的思路就是:重寫此方法,將這兩個值換成能有效獲取實(shí)際寬高的方式即可
// 我的方案是換成了 `document.body.getBoundingClientRect()` 的 `width` 和 `height`
// 但這個邏輯依賴于 `body` 本身的寬高,所以請根據(jù)自己的情況換成更合適你的方案食用~
Popper.prototype._getBoundaries = function (data, padding, boundariesElement) {
// NOTE: 1 DOM access here
var boundaries = {};
var width, height;
if (boundariesElement === "window") {
var body = root.document.body,
html = root.document.documentElement;
height = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
width = Math.max(
body.scrollWidth,
body.offsetWidth,
html.clientWidth,
html.scrollWidth,
html.offsetWidth
);
boundaries = {
top: 0,
right: width,
bottom: height,
left: 0,
};
} else if (boundariesElement === "viewport") {
var offsetParent = getOffsetParent(this._popper);
var scrollParent = getScrollParent(this._popper);
var offsetParentRect = getOffsetRect(offsetParent);
// Thanks the fucking native API, `document.body.scrollTop` & `document.documentElement.scrollTop`
var getScrollTopValue = function getScrollTopValue(element) {
return element == document.body
? Math.max(document.documentElement.scrollTop, document.body.scrollTop)
: element.scrollTop;
};
var getScrollLeftValue = function getScrollLeftValue(element) {
return element == document.body
? Math.max(
document.documentElement.scrollLeft,
document.body.scrollLeft
)
: element.scrollLeft;
};
// if the popper is fixed we don't have to substract scrolling from the boundaries
var scrollTop =
data.offsets.popper.position === "fixed"
? 0
: getScrollTopValue(scrollParent);
var scrollLeft =
data.offsets.popper.position === "fixed"
? 0
: getScrollLeftValue(scrollParent);
boundaries = {
top: 0 - (offsetParentRect.top - scrollTop),
// right: root.document.documentElement.clientWidth - (offsetParentRect.left - scrollLeft),
right:
root.document.body.getBoundingClientRect().width -
(offsetParentRect.left - scrollLeft),
// bottom: root.document.documentElement.clientHeight - (offsetParentRect.top - scrollTop),
bottom:
root.document.body.getBoundingClientRect().height -
(offsetParentRect.top - scrollTop),
left: 0 - (offsetParentRect.left - scrollLeft),
};
} else {
if (getOffsetParent(this._popper) === boundariesElement) {
boundaries = {
top: 0,
left: 0,
right: boundariesElement.clientWidth,
bottom: boundariesElement.clientHeight,
};
} else {
boundaries = getOffsetRect(boundariesElement);
}
}
boundaries.left += padding;
boundaries.right -= padding;
boundaries.top = boundaries.top + padding;
boundaries.bottom = boundaries.bottom - padding;
return boundaries;
};
// 注:以下 function 為上面重寫 function 的依賴,均完整復(fù)制自 Popper.js
/**
* Returns the offset parent of the given element
* @function
* @ignore
* @argument {Element} element
* @returns {Element} offset parent
*/
function getOffsetParent(element) {
// NOTE: 1 DOM access here
var offsetParent = element.offsetParent;
return offsetParent === root.document.body || !offsetParent
? root.document.documentElement
: offsetParent;
}
/**
* Returns the scrolling parent of the given element
* @function
* @ignore
* @argument {Element} element
* @returns {Element} offset parent
*/
function getScrollParent(element) {
var parent = element.parentNode;
if (!parent) {
return element;
}
if (parent === root.document) {
// Firefox puts the scrollTOp value on `documentElement` instead of `body`, we then check which of them is
// greater than 0 and return the proper element
if (root.document.body.scrollTop || root.document.body.scrollLeft) {
return root.document.body;
} else {
return root.document.documentElement;
}
}
// Firefox want us to check `-x` and `-y` variations as well
if (
["scroll", "auto"].indexOf(getStyleComputedProperty(parent, "overflow")) !==
-1 ||
["scroll", "auto"].indexOf(
getStyleComputedProperty(parent, "overflow-x")
) !== -1 ||
["scroll", "auto"].indexOf(
getStyleComputedProperty(parent, "overflow-y")
) !== -1
) {
// If the detected scrollParent is body, we perform an additional check on its parentNode
// in this way we'll get body if the browser is Chrome-ish, or documentElement otherwise
// fixes issue #65
return parent;
}
return getScrollParent(element.parentNode);
}
/**
* Get CSS computed property of the given element
* @function
* @ignore
* @argument {Eement} element
* @argument {String} property
*/
function getStyleComputedProperty(element, property) {
// NOTE: 1 DOM access here
var css = root.getComputedStyle(element, null);
return css[property];
}
/**
* Get the position of the given element, relative to its offset parent
* @function
* @ignore
* @param {Element} element
* @return {Object} position - Coordinates of the element and its `scrollTop`
*/
function getOffsetRect(element) {
var elementRect = {
width: element.offsetWidth,
height: element.offsetHeight,
left: element.offsetLeft,
top: element.offsetTop,
};
elementRect.right = elementRect.left + elementRect.width;
elementRect.bottom = elementRect.top + elementRect.height;
// position
return elementRect;
}
/**
* Get offsets to the popper
* @method
* @memberof Popper
* @access private
* @param {Element} popper - the popper element
* @param {Element} reference - the reference element (the popper will be relative to this)
* @returns {Object} An object containing the offsets which will be applied to the popper
*/
Popper.prototype._getOffsets = function (popper, reference, placement) {
placement = placement.split("-")[0];
var popperOffsets = {};
popperOffsets.position = this.state.position;
var isParentFixed = popperOffsets.position === "fixed";
//
// Get reference element position
//
var referenceOffsets = getOffsetRectRelativeToCustomParent(
reference,
getOffsetParent(popper),
isParentFixed
);
//
// Get popper sizes
//
var popperRect = getOuterSizes(popper);
//
// Compute offsets of popper
//
// depending by the popper placement we have to compute its offsets slightly differently
if (["right", "left"].indexOf(placement) !== -1) {
popperOffsets.top =
referenceOffsets.top +
referenceOffsets.height / 2 -
popperRect.height / 2;
if (placement === "left") {
popperOffsets.left = referenceOffsets.left - popperRect.width;
} else {
popperOffsets.left = referenceOffsets.right;
}
} else {
popperOffsets.left =
referenceOffsets.left + referenceOffsets.width / 2 - popperRect.width / 2;
if (placement === "top") {
popperOffsets.top = referenceOffsets.top - popperRect.height;
} else {
popperOffsets.top = referenceOffsets.bottom;
}
}
// Add width and height to our offsets object
popperOffsets.width = popperRect.width;
popperOffsets.height = popperRect.height;
return {
popper: popperOffsets,
reference: referenceOffsets,
};
};
/**
* Given an element and one of its parents, return the offset
* @function
* @ignore
* @param {HTMLElement} element
* @param {HTMLElement} parent
* @return {Object} rect
*/
function getOffsetRectRelativeToCustomParent(element, parent, fixed) {
var elementRect = getBoundingClientRect(element);
var parentRect = getBoundingClientRect(parent,fixed);
if (fixed) {
var scrollParent = getScrollParent(parent);
parentRect.top += scrollParent.scrollTop;
parentRect.bottom += scrollParent.scrollTop;
parentRect.left += scrollParent.scrollLeft;
parentRect.right += scrollParent.scrollLeft;
}
var rect = {
top: elementRect.top - parentRect.top,
left: elementRect.left - parentRect.left,
bottom: elementRect.top - parentRect.top + elementRect.height,
right: elementRect.left - parentRect.left + elementRect.width,
width: elementRect.width,
height: elementRect.height,
};
return rect;
}
/**
* Get the outer sizes of the given element (offset size + margins)
* @function
* @ignore
* @argument {Element} element
* @returns {Object} object containing width and height properties
*/
function getOuterSizes(element) {
// NOTE: 1 DOM access here
var _display = element.style.display,
_visibility = element.style.visibility;
element.style.display = "block";
element.style.visibility = "hidden";
var calcWidthToForceRepaint = element.offsetWidth;
// original method
var styles = root.getComputedStyle(element);
var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
var result = {
width: element.offsetWidth + y,
height: element.offsetHeight + x,
};
// reset element styles
element.style.display = _display;
element.style.visibility = _visibility;
return result;
}
function getBoundingClientRect(element,fixed) {
var rect = element.getBoundingClientRect();
// whether the IE version is lower than 11
var isIE = navigator.userAgent.indexOf("MSIE") != -1;
// fix ie document bounding top always 0 bug
var rectTop =
isIE && element.tagName === "HTML" ? -element.scrollTop : rect.top;
var zoom = window.getComputedStyle(document.body).zoom;
const visualtop = fixed?rectTop * zoom:rectTop; // 視覺左偏移(物理像素)
return {
left: rect.left,
top: visualtop,
right: rect.right,
bottom: rect.bottom,
width: rect.right - rect.left,
height: rect.bottom - rectTop,
};
}
剩余的細(xì)節(jié)問題,需要同志們自己去查看微調(diào)!