假设我们有以下设置。
#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
, perspective
或 filter
属性设置。
对于 absolute
或 relative
定位的元素,我们有 elem.offsetParent
这给了我们这个参考。然而,它被设置为空,因为 fixed
元素。
当然,我可以查找dom,找到第一个元素,它的样式属性是 transform
, perspective
或 filter
集,但这似乎是黑客,而不是未来的证明。
谢谢!
已知的行为,符合规范,但可能需要修改规范。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>