为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

繁花如伊 2022-05-26 10:40:31
假设我们有以下设置:#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,perspective或filter属性的祖先,它甚至可能是 documentElement。对于absolute或relative定位的元素,我们有elem.offsetParent这给了我们这个参考。fixed但是,对于元素,它设置为 null 。当然,我可以查找 dom 并找到第一个具有或set 样式属性的元素transform,但这似乎很老套,而且没有未来的证明。perspectivefilter谢谢!
查看完整描述

2 回答

?
慕工程0101907

TA贡献1887条经验 获得超5个赞

已知行为和规范兼容。规格可能应该改变。

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

展开片段

取自 popper.js 的解决方法代码。似乎没有让 doc.body 正确。唯一专门处理 TH TD TABLE 的。dom-helpers 应该可以工作,因为它使用 offsetParent 进行遍历。 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>


查看完整回答
反对 回复 2022-05-26
?
侃侃无极

TA贡献2051条经验 获得超10个赞

我最近为这个不那么小、长期存在的怪癖设计了一个我觉得相当优雅的解决方法。我设计了一个CustomElement,它可以自动检测它是否已在包含块内使用,如果是,则将其自身从 DOM 中的当前位置转移到 body 元素的末尾。


感谢这个对类似问题的回答,为我指明了正确的方向。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>



查看完整回答
反对 回复 2022-05-26
  • 2 回答
  • 0 关注
  • 368 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号