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

假设我们有以下设置:


#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.org/en-US/docs/Web/CSS/position#fixed

有没有一种简单的方法可以找出按钮的包含块?top: 50px计算的元素是哪个?假设您没有对包含块的引用,并且您不知道它有多少层。如果没有设置transform,perspectivefilter属性的祖先,它甚至可能是 documentElement。

对于absoluterelative定位的元素,我们有elem.offsetParent这给了我们这个参考。fixed但是,对于元素,它设置为 null 。

当然,我可以查找 dom 并找到第一个具有或set 样式属性的元素transform,但这似乎很老套,而且没有未来的证明。perspectivefilter

谢谢!


繁花如伊
浏览 369回答 2
2回答

慕工程0101907

已知行为和规范兼容。规格可能应该改变。https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent我已经包含了来自各种库的一些解决方法。来自 dom-helpers 的解决方法(似乎是最一致的,并且使用 offsetParent 进行遍历意味着它应该只真正遍历一次或两次。):https ://github.com/react-bootstrap/dom-helpers/blob/master /src/offsetParent.ts// taken from popper.jsfunction getStyleComputedProperty(element, property) {&nbsp; if (element.nodeType !== 1) {&nbsp; &nbsp; return [];&nbsp; }&nbsp; // NOTE: 1 DOM access here&nbsp; const window = element.ownerDocument.defaultView;&nbsp; const css = window.getComputedStyle(element, null);&nbsp; return property ? css[property] : css;}getOffsetParent = function(node) {&nbsp; const doc = (node && node.ownerDocument) || document&nbsp; const isHTMLElement = e => !!e && 'offsetParent' in e&nbsp; let parent = node && node.offsetParent&nbsp; while (&nbsp; &nbsp; isHTMLElement(parent) &&&nbsp; &nbsp; parent.nodeName !== 'HTML' &&&nbsp; &nbsp; getComputedStyle(parent, 'position') === 'static'&nbsp; ) {&nbsp; &nbsp; parent = parent.offsetParent&nbsp; }&nbsp; return (parent || doc.documentElement)}#header {&nbsp; &nbsp; background-color: #ddd;&nbsp; &nbsp; padding: 2rem;}#containing-block {&nbsp; &nbsp; background-color: #eef;&nbsp; &nbsp; padding: 2rem;&nbsp; &nbsp; height: 70px;&nbsp; &nbsp; transform: translate(0, 0);}#button {&nbsp; &nbsp; position: fixed;&nbsp; &nbsp; top: 50px;}<div id="header">header</div>&nbsp; &nbsp; <div id="containing-block">&nbsp; &nbsp; &nbsp; &nbsp; containing-block&nbsp; &nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <button id="button" onclick="console.log('offsetParent', getOffsetParent(this),this.offsetParent)">click me</button>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; </div>展开片段取自 jQuery 源代码的解决方法代码。不处理非元素,也不处理 TABLE TH TD,但它是jQuery。 https://github.com/jquery/jquery/blob/master/src/offset.js// taken from popper.jsfunction getStyleComputedProperty(element, property) {&nbsp; if (element.nodeType !== 1) {&nbsp; &nbsp; return [];&nbsp; }&nbsp; // NOTE: 1 DOM access here&nbsp; const window = element.ownerDocument.defaultView;&nbsp; const css = window.getComputedStyle(element, null);&nbsp; return property ? css[property] : css;}getOffsetParent = function(elem) {&nbsp; var doc = elem.ownerDocument;&nbsp; var offsetParent = elem.offsetParent || doc.documentElement;&nbsp; while (offsetParent &&&nbsp; &nbsp; (offsetParent !== doc.body || offsetParent !== doc.documentElement) &&&nbsp; &nbsp; getComputedStyle(offsetParent, "position") === "static") {&nbsp; &nbsp; offsetParent = offsetParent.parentNode;&nbsp; }&nbsp; return offsetParent;}#header {&nbsp; &nbsp; background-color: #ddd;&nbsp; &nbsp; padding: 2rem;}#containing-block {&nbsp; &nbsp; background-color: #eef;&nbsp; &nbsp; padding: 2rem;&nbsp; &nbsp; height: 70px;&nbsp; &nbsp; transform: translate(0, 0);}#button {&nbsp; &nbsp; position: fixed;&nbsp; &nbsp; top: 50px;}<div id="header">header</div>&nbsp; &nbsp; <div id="containing-block">&nbsp; &nbsp; &nbsp; &nbsp; containing-block&nbsp; &nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <button id="button" onclick="console.log('offsetParent', getOffsetParent(this),this.offsetParent)">click me</button>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; </div>展开片段取自 popper.js 的解决方法代码。似乎没有让 doc.body 正确。唯一专门处理 TH TD TABLE 的。dom-helpers 应该可以工作,因为它使用 offsetParent 进行遍历。 https://github.com/popperjs/popper-core/blob/master/src/dom-utils/getOffsetParent.jsvar 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) {&nbsp; if (version === 11) {&nbsp; &nbsp; return isIE11;&nbsp; }&nbsp; if (version === 10) {&nbsp; &nbsp; return isIE10;&nbsp; }&nbsp; return isIE11 || isIE10;}function getStyleComputedProperty(element, property) {&nbsp; if (element.nodeType !== 1) {&nbsp; &nbsp; return [];&nbsp; }&nbsp; // NOTE: 1 DOM access here&nbsp; const window = element.ownerDocument.defaultView;&nbsp; const css = window.getComputedStyle(element, null);&nbsp; return property ? css[property] : css;}function getOffsetParent(element) {&nbsp; if (!element) {&nbsp; &nbsp; return document.documentElement;&nbsp; }&nbsp; const noOffsetParent = isIE(10) ? document.body : null;&nbsp; // NOTE: 1 DOM access here&nbsp; let offsetParent = element.offsetParent || null;&nbsp; // Skip hidden elements which don't have an offsetParent&nbsp; while (offsetParent === noOffsetParent && element.nextElementSibling) {&nbsp; &nbsp; offsetParent = (element = element.nextElementSibling).offsetParent;&nbsp; }&nbsp; const nodeName = offsetParent && offsetParent.nodeName;&nbsp; if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {&nbsp; &nbsp; return element ? element.ownerDocument.documentElement : document.documentElement;&nbsp; }&nbsp; // .offsetParent will return the closest TH, TD or TABLE in case&nbsp; // no offsetParent is present, I hate this job...&nbsp; if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {&nbsp; &nbsp; return getOffsetParent(offsetParent);&nbsp; }&nbsp; return offsetParent;}#header {&nbsp; &nbsp; background-color: #ddd;&nbsp; &nbsp; padding: 2rem;}#containing-block {&nbsp; &nbsp; background-color: #eef;&nbsp; &nbsp; padding: 2rem;&nbsp; &nbsp; height: 70px;&nbsp; &nbsp; transform: translate(0, 0);}#button {&nbsp; &nbsp; position: fixed;&nbsp; &nbsp; top: 50px;}<div id="header">header</div><div id="containing-block">&nbsp; &nbsp; containing-block&nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; <div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <button id="button" onclick="console.log('offsetParent', getOffsetParent(this))">click me</button>&nbsp; &nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; &nbsp; </div>&nbsp; &nbsp; </div></div>

侃侃无极

我最近为这个不那么小、长期存在的怪癖设计了一个我觉得相当优雅的解决方法。我设计了一个CustomElement,它可以自动检测它是否已在包含块内使用,如果是,则将其自身从 DOM 中的当前位置转移到 body 元素的末尾。感谢这个对类似问题的回答,为我指明了正确的方向。https://stackoverflow.com/a/65155438/6036546<!DOCTYPE html><title> Breakout Fixed </title><script type="module">&nbsp; customElements.define(&nbsp; &nbsp; 'breakout-fixed',&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; class BreakoutFixed extends HTMLElement {&nbsp; &nbsp; &nbsp; constructor() {&nbsp; &nbsp; &nbsp; &nbsp; super();&nbsp; &nbsp; &nbsp; &nbsp; this.attachShadow({ mode : 'open' });&nbsp; &nbsp; &nbsp; &nbsp; this.shadowRoot.innerHTML = this.template;&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; get template() {&nbsp; &nbsp; &nbsp; &nbsp; return `&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <style> :host { position: fixed; } </style>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <slot></slot>&nbsp; &nbsp; &nbsp; &nbsp; `;&nbsp;&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; breakout() {&nbsp; &nbsp; &nbsp; &nbsp; const el = this;&nbsp; &nbsp; &nbsp; &nbsp; if (this.fixed !== true) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; window.addEventListener('resize', el.fix);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.fixed = true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; if (el.parentNode == document.body) { return; }&nbsp; &nbsp; &nbsp; &nbsp; function shift() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getContainingBlock(el) &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; document.body.append(el);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; function getContainingBlock(node) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (node.parentElement) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (node.parentElement == document.body) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return document.body;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if (testNode(node.parentElement) == false) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return getContainingBlock(node.parentElement);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { return node.parentElement; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { return null; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; function testNode(node) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let test; let cs = getComputedStyle(node);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; test = cs.getPropertyValue('position'); if ([&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'absolute', 'fixed'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ].includes(test)) { return true; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; test = cs.getPropertyValue('transform');&nbsp; &nbsp;if (test != 'none')&nbsp; { return true; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; test = cs.getPropertyValue('perspective'); if (test != 'none')&nbsp; { return true; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; test = cs.getPropertyValue('perspective'); if (test != 'none')&nbsp; { return true; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; test = cs.getPropertyValue('filter');&nbsp; &nbsp; &nbsp; if (test != 'none')&nbsp; { return true; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; test = cs.getPropertyValue('contain');&nbsp; &nbsp; &nbsp;if (test == 'paint') { return true; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; test = cs.getPropertyValue('will-change'); if ([&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 'transform', 'perspective', 'filter'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ].includes(test)) { return true; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; connectedCallback() {&nbsp; &nbsp; &nbsp; &nbsp; this.breakout();&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; );</script><style>&nbsp; body { background: dimgrey; }&nbsp;&nbsp;&nbsp; #container {&nbsp; &nbsp; height: 300px;&nbsp; &nbsp; width: 50%;&nbsp; &nbsp; background: dodgerblue;&nbsp; &nbsp; transform: scale(2);&nbsp; }&nbsp;&nbsp;&nbsp; div#test {&nbsp; &nbsp; position: fixed;&nbsp; &nbsp; right: 0;&nbsp; &nbsp; bottom: 0;&nbsp; &nbsp; padding: 1rem;&nbsp; &nbsp; background: red;&nbsp; }&nbsp;&nbsp;&nbsp; breakout-fixed {&nbsp; &nbsp; top: 0; right: 0;&nbsp; &nbsp; padding: 1rem;&nbsp; &nbsp; background: limegreen;&nbsp; &nbsp; transform: scale(3);&nbsp; &nbsp; transform-origin: top right;&nbsp; }</style><div id="container">&nbsp; <div id="test"> This element will be fixed to it's containing block. </div>&nbsp; <breakout-fixed>&nbsp; &nbsp; <div> This element will be fixed to the viewport. </div>&nbsp; </breakout-fixed></div>
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript