2 回答
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>
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>
添加回答
举报
