简体   繁体   English

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

[英]How can I get the containing block of a "fixed" positioned element with javascript?

Let's say we have the following setup:假设我们有以下设置:

 #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>

where the button has fixed position and the containing-block has a transform property in place.其中按钮具有fixed位置并且包含块具有适当的transform属性。

This might come as a surprise, but the button is positioned relative to the #containing-block , not the viewport (as one would expect when using fixed ).这可能会让人感到意外,但按钮的位置是相对于#containing-block ,而不是视口(正如人们在使用fixed时所期望的那样)。 That's because the #containing-block element has the transform property set.这是因为#containing-block元素设置了transform属性。 Seehttps://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed for clarification.有关说明,请参阅https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed

Is there an easy way to find out which is the containing block of the button?有没有一种简单的方法可以找出按钮的包含块? Which is the element top: 50px is calculated in respect to? top: 50px是相对于哪个元素计算的? Assume you don't have a reference to the containing block and you don't know how many levels up it is.假设您没有对包含块的引用,并且您不知道它有多少级。 It may even be the documentElement if there are no ancestors with transform , perspective or filter properties set.如果没有设置transformperspectivefilter属性的祖先,它甚至可能是 documentElement。

For absolute or relative positioned elements, we have elem.offsetParent which gives us this reference.对于absoluterelative定位的元素,我们有elem.offsetParent给我们这个引用。 However, it is set to null for fixed elements.但是,对于fixed元素,它被设置为 null。

Of course, I could look up the dom and find the first element that has a style property of transform , perspective or filter set, but this seems hacky and not future proof.当然,我可以查找 dom 并找到第一个具有transformperspectivefilter set 样式属性的元素,但这看起来很笨拙,而且无法证明未来。

Thanks!谢谢!

Known behavior and spec compliant.已知行为和规范兼容。 spec should probably be changed though.规格可能应该改变。
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent

I've included a few workarounds from various libraries.我已经包含了来自各种库的一些解决方法。

Workaround taken from dom-helpers (seems to be most consistent and use of offsetParent to traverse means it should only ever really traverse once or twice.):从 dom-helpers 采取的解决方法(似乎是最一致的,使用 offsetParent 进行遍历意味着它应该只真正遍历一两次。):
https://github.com/react-bootstrap/dom-helpers/blob/master/src/offsetParent.ts https://github.com/react-bootstrap/dom-helpers/blob/master/src/offsetParent.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>

Workaround code taken from jQuery source.取自 jQuery 源代码的解决方法代码。 Doesn't deal with non-element, nor TABLE TH TD, but it's jQuery .不处理非元素,也不处理 TABLE TH TD,但它是jQuery https://github.com/jquery/jquery/blob/master/src/offset.js https://github.com/jquery/jquery/blob/master/src/offset.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>

Workaround code taken from popper.js.取自 popper.js 的解决方法代码。 Doesn't seem to get doc.body right.似乎没有得到 doc.body 正确。 The only one that specifically deals with TH TD TABLE.唯一一个专门处理 TH TD TABLE 的。 dom-helpers should work just because it uses offsetParent to traverse. dom-helpers 应该可以工作,因为它使用 offsetParent 进行遍历。 https://github.com/popperjs/popper-core/blob/master/src/dom-utils/getOffsetParent.js https://github.com/popperjs/popper-core/blob/master/src/dom-utils/getOffsetParent.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>

I recently crafted what I feel to be a rather elegant workaround to this not-so-little, long-standing quirk.我最近精心设计了一种我认为相当优雅的解决方法,以解决这个不太小的、长期存在的怪癖。 I've designed a CustomElement that can automatically detect if it has been used inside of a containing block and, if so, shift itself from it's current location in the DOM to the end of the body element.我设计了一个CustomElement ,它可以自动检测它是否已在包含块内使用,如果是,则将自身从 DOM 中的当前位置移动到 body 元素的末尾。

Credit to this answer to a similar question for pointing me in the right direction.归功于此对类似问题的回答为我指明了正确的方向。 https://stackoverflow.com/a/65155438/6036546 https://stackoverflow.com/a/65155438/6036546

<!DOCTYPE html>
<title> Breakout Fixed </title>
<script type="module">
  customElements.define(
    'breakout-fixed',
    
    class BreakoutFixed extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({ mode : 'open' });
        this.shadowRoot.innerHTML = this.template;
      }
      
      get template() {
        return `
          <style> :host { position: fixed; } </style>
          <slot></slot>
        `; 
      }
      
      breakout() {
        const el = this;

        if (this.fixed !== true) {
          window.addEventListener('resize', el.fix);
          this.fixed = true;
        }
        
        if (el.parentNode == document.body) { return; }

        function shift() {
          getContainingBlock(el) &&
            document.body.append(el);
        }

        function getContainingBlock(node) {
          if (node.parentElement) {
            if (node.parentElement == document.body) {
              return document.body;
            } else if (testNode(node.parentElement) == false) {
              return getContainingBlock(node.parentElement);
            } else { return node.parentElement; }
          } else { return null; }
          function testNode(node) {
            let test; let cs = getComputedStyle(node);
            test = cs.getPropertyValue('position'); if ([
              'absolute', 'fixed'
            ].includes(test)) { return true; }
            test = cs.getPropertyValue('transform');   if (test != 'none')  { return true; }
            test = cs.getPropertyValue('perspective'); if (test != 'none')  { return true; }
            test = cs.getPropertyValue('perspective'); if (test != 'none')  { return true; }
            test = cs.getPropertyValue('filter');      if (test != 'none')  { return true; }
            test = cs.getPropertyValue('contain');     if (test == 'paint') { return true; }
            test = cs.getPropertyValue('will-change'); if ([
              'transform', 'perspective', 'filter'
            ].includes(test)) { return true; }
            return false;
          }
        }
      }
      
      connectedCallback() {
        this.breakout();
      }
    }
  );
</script>
<style>
  body { background: dimgrey; }
  
  #container {
    height: 300px;
    width: 50%;
    background: dodgerblue;
    transform: scale(2);
  }
  
  div#test {
    position: fixed;
    right: 0;
    bottom: 0;
    padding: 1rem;
    background: red;
  }
  
  breakout-fixed {
    top: 0; right: 0;
    padding: 1rem;
    background: limegreen;
    transform: scale(3);
    transform-origin: top right;
  }
</style>
<div id="container">
  <div id="test"> This element will be fixed to it's containing block. </div>
  <breakout-fixed>
    <div> This element will be fixed to the viewport. </div>
  </breakout-fixed>
</div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何强制固定定位元素内的元素响应Android Web浏览器上的javascript onclick事件? - How can I force elements inside a fixed positioned element respond to javascript onclick events on the Android web browser? 如何获取相对于文档固定位置的元素的offsetTop - How to get offsetTop for fixed positioned element relative to the document 如何将固定定位的元素堆叠在静态定位的元素前面? - How do I stack a fixed positioned element in front of a statically positioned element? 如何在javascript中获取绝对元素的祖先定位元素? - how to get ancestor positioned element of an absolute element in javascript? 如何获取包含Javascript / Jquery中单击文本的元素? - How do I get the element containing clicked text in Javascript/Jquery? 直接使用原生 JavaScript 或 jQuery 方法获取元素的包含块 - Direct native JavaScript or jQuery method to get containing block of an element 滚动时如何在固定元素下获取默认定位元素 go? - How to get a default positioned element go under a fixed element while scrolling? 固定位置的元素顶部 - Fixed positioned element Top 修复定位元素问题 - Fixed positioned element issue 我如何获得具有由Javascript设置的固定位置的DIV,以使其保留在包含的DIV中? - How do I get this DIV which has fixed position set by Javascript to stay within the containing DIV?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM