element-zoom-bug.js

解決 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)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前端開發(fā)面試題 面試題目: 根據(jù)你的等級和職位的變化,入門級到專家級,廣度和深度都會有所增加。 題目類型: 理論知...
    怡寶丶閱讀 2,678評論 0 7
  • 前端開發(fā)面試題 <a name='preface'>前言</a> 只看問題點(diǎn)這里 看全部問題和答案點(diǎn)這里 本文由我...
    自you是敏感詞閱讀 904評論 0 3
  • 資料 element-ui 中文官網(wǎng)[http://element-cn.eleme.io/#/zh-CN]ele...
    sunxiaochuan閱讀 75,403評論 7 60
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,159評論 4 61
  • 我之前寫前端的時(shí)候就三個技術(shù)(html、js、css),現(xiàn)在的前端技術(shù)一般使用vue.js+element-ui,...
    thekings閱讀 1,071評論 0 2

友情鏈接更多精彩內(nèi)容