[英]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.如果没有设置transform
、 perspective
或filter
属性的祖先,它甚至可能是 documentElement。
For absolute
or relative
positioned elements, we have elem.offsetParent
which gives us this reference.对于absolute
或relative
定位的元素,我们有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 并找到第一个具有transform
、 perspective
或filter
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.