如何用javascript获取 "固定 "定位元素的包含块?

问题描述 投票:8回答:1

假设我们有以下设置。

#header {
    background-color: #ddd;
    padding: 2rem;
}
#containing-block {
    background-color: #eef;
    padding: 2rem;
    height: 70px;
    transform: translate(0, 0);
}
#button {
    position: fixed;
    top: 50px;
}
<div id="header">header</div>
<div id="containing-block">
    containing-block
    <div>
      <div>
        <div>
          <button id="button" onclick="console.log('offsetParent', this.offsetParent)">click me</button>
        </div>
      </div>
    </div>
</div>

其中按钮有 fixed 的位置,并且包含块有一个 transform 属性的位置。

这可能会让人感到惊讶,但按钮的位置是相对于 #containing-block视图,而不是视口(就像人们在使用 fixed). 这是因为 #containing-block 元素具有 transform 属性集。见 https:/developer.mozilla.orgen-USdocsWebCSSposition#fixed。 以供说明。

有没有一种简单的方法可以查出哪个是按钮的包含块?哪一个是元素 top: 50px 是根据什么来计算的?假设你没有对包含块的引用,你也不知道它是几级。如果没有任何祖先,它甚至可能是documentElement的 transform, perspectivefilter 属性设置。

对于 absoluterelative 定位的元素,我们有 elem.offsetParent 这给了我们这个参考。然而,它被设置为空,因为 fixed 元素。

当然,我可以查找dom,找到第一个元素,它的样式属性是 transform, perspectivefilter 集,但这似乎是黑客,而不是未来的证明。

谢谢!

javascript css-position
1个回答
2
投票

已知的行为,符合规范,但可能需要修改规范。https:/developer.mozilla.orgen-USdocsWebAPIHTMLEmentoffsetParent。

我已经包含了一些来自不同库的变通方法。

工作方法来自dom-helpers(似乎是最一致的,使用offsetParent来遍历意味着它应该只真正遍历一次或两次)。https:/github.comreact-bootstrapdom-helpersblobmastersrcoffsetParent.ts。

// taken from popper.js
function getStyleComputedProperty(element, property) {
  if (element.nodeType !== 1) {
    return [];
  }
  // NOTE: 1 DOM access here
  const window = element.ownerDocument.defaultView;
  const css = window.getComputedStyle(element, null);
  return property ? css[property] : css;
}

getOffsetParent = function(node) {
  const doc = (node && node.ownerDocument) || document
  const isHTMLElement = e => !!e && 'offsetParent' in e
  let parent = node && node.offsetParent

  while (
    isHTMLElement(parent) &&
    parent.nodeName !== 'HTML' &&
    getComputedStyle(parent, 'position') === 'static'
  ) {
    parent = parent.offsetParent
  }

  return (parent || doc.documentElement)
}
#header {
    background-color: #ddd;
    padding: 2rem;
}
#containing-block {
    background-color: #eef;
    padding: 2rem;
    height: 70px;
    transform: translate(0, 0);
}
#button {
    position: fixed;
    top: 50px;
}
<div id="header">header</div>
    <div id="containing-block">
        containing-block
        <div>
          <div>
            <div>
              <button id="button" onclick="console.log('offsetParent', getOffsetParent(this),this.offsetParent)">click me</button>
            </div>
          </div>
        </div>
    </div>

取自jQuery源码的变通代码。 不处理非元素,也不处理TABLE TH TD,但它是一个很好的解决方案。jQuery.https:/github.comjqueryjqueryblobmastersrcoffset.js。

// taken from popper.js
function getStyleComputedProperty(element, property) {
  if (element.nodeType !== 1) {
    return [];
  }
  // NOTE: 1 DOM access here
  const window = element.ownerDocument.defaultView;
  const css = window.getComputedStyle(element, null);
  return property ? css[property] : css;
}

getOffsetParent = function(elem) {
  var doc = elem.ownerDocument;
  var offsetParent = elem.offsetParent || doc.documentElement;
  while (offsetParent &&
    (offsetParent !== doc.body || offsetParent !== doc.documentElement) &&
    getComputedStyle(offsetParent, "position") === "static") {

    offsetParent = offsetParent.parentNode;
  }
  return offsetParent;
}
#header {
    background-color: #ddd;
    padding: 2rem;
}
#containing-block {
    background-color: #eef;
    padding: 2rem;
    height: 70px;
    transform: translate(0, 0);
}
#button {
    position: fixed;
    top: 50px;
}
<div id="header">header</div>
    <div id="containing-block">
        containing-block
        <div>
          <div>
            <div>
              <button id="button" onclick="console.log('offsetParent', getOffsetParent(this),this.offsetParent)">click me</button>
            </div>
          </div>
        </div>
    </div>

从popper.js中提取的变通代码。 似乎没有得到doc.body的权利。 唯一一个专门处理TH TD TABLE的。dom-helpers应该可以工作,只是因为它使用offsetParent来遍历。https:/github.compopperjspopper-coreblobmastersrcdom-utilsgetOffsetParent.js。

var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined';

const isIE11 = isBrowser && !!(window.MSInputMethodContext && document.documentMode);
const isIE10 = isBrowser && /MSIE 10/.test(navigator.userAgent);
function isIE(version) {
  if (version === 11) {
    return isIE11;
  }
  if (version === 10) {
    return isIE10;
  }
  return isIE11 || isIE10;
}

function getStyleComputedProperty(element, property) {
  if (element.nodeType !== 1) {
    return [];
  }
  // NOTE: 1 DOM access here
  const window = element.ownerDocument.defaultView;
  const css = window.getComputedStyle(element, null);
  return property ? css[property] : css;
}

function getOffsetParent(element) {
  if (!element) {
    return document.documentElement;
  }

  const noOffsetParent = isIE(10) ? document.body : null;

  // NOTE: 1 DOM access here
  let offsetParent = element.offsetParent || null;
  // Skip hidden elements which don't have an offsetParent
  while (offsetParent === noOffsetParent && element.nextElementSibling) {
    offsetParent = (element = element.nextElementSibling).offsetParent;
  }

  const nodeName = offsetParent && offsetParent.nodeName;

  if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
    return element ? element.ownerDocument.documentElement : document.documentElement;
  }

  // .offsetParent will return the closest TH, TD or TABLE in case
  // no offsetParent is present, I hate this job...
  if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
    return getOffsetParent(offsetParent);
  }

  return offsetParent;
}
#header {
    background-color: #ddd;
    padding: 2rem;
}
#containing-block {
    background-color: #eef;
    padding: 2rem;
    height: 70px;
    transform: translate(0, 0);
}
#button {
    position: fixed;
    top: 50px;
}
<div id="header">header</div>
<div id="containing-block">
    containing-block
    <div>
      <div>
        <div>
          <button id="button" onclick="console.log('offsetParent', getOffsetParent(this))">click me</button>
        </div>
      </div>
    </div>
</div>
© www.soinside.com 2019 - 2024. All rights reserved.