Skip to content

Commit

Permalink
fix(isVisibleOnScreen): account for position: absolute elements insid…
Browse files Browse the repository at this point in the history
…e overflow container (#4405)

Also tested various ways to try to get the `position: absolute` to be
hidden by the node. Turns out there are a few cases where it will be
hidden:

* overflow node uses position itself other than static
* node in-between the overflow node and the positioned child uses
position `relative` or `sticky`

and cases where it won't be hidden

* positioned child uses a position of `fixed` (it won't be hidden by any
ancestor overflow, even if the ancestor uses position itself)

Closes: #4016
  • Loading branch information
straker committed Apr 15, 2024
1 parent 56e139a commit 2940f6e
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 3 deletions.
40 changes: 37 additions & 3 deletions lib/commons/dom/visibility-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,33 @@ export function overflowHidden(vNode, { isAncestor } = {}) {
return false;
}

const rect = vNode.boundingClientRect;
const nodes = getOverflowHiddenAncestors(vNode);
// a node with position fixed cannot be hidden by an overflow
// ancestor, even when that ancestor uses a non-static position
const position = vNode.getComputedStylePropertyValue('position');
if (position === 'fixed') {
return false;
}

const nodes = getOverflowHiddenAncestors(vNode);
if (!nodes.length) {
return false;
}

const rect = vNode.boundingClientRect;
return nodes.some(node => {
const nodeRect = node.boundingClientRect;
// a node with position absolute will not be hidden by an
// overflow ancestor unless the ancestor uses a non-static
// position or a node in-between uses a position of relative
// or sticky
if (
position === 'absolute' &&
!hasPositionedAncestorBetween(vNode, node) &&
node.getComputedStylePropertyValue('position') === 'static'
) {
return false;
}

const nodeRect = node.boundingClientRect;
if (nodeRect.width < 2 || nodeRect.height < 2) {
return true;
}
Expand Down Expand Up @@ -238,3 +255,20 @@ export function detailsHidden(vNode) {

return !vNode.parent.hasAttr('open');
}

function hasPositionedAncestorBetween(child, ancestor) {
let node = child.parent;
while (node && node !== ancestor) {
if (
['relative', 'sticky'].includes(
node.getComputedStylePropertyValue('position')
)
) {
return true;
}

node = node.parent;
}

return false;
}
91 changes: 91 additions & 0 deletions test/commons/dom/visibility-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,97 @@ describe('dom.visibility-methods', () => {
);
assert.isFalse(overflowHidden(vNode));
});

it('should return false for ancestor with "overflow:hidden" and element with "position:fixed"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px;">' +
'<div id="target" style="margin-left: 100px; position: fixed">Hello world</div>' +
'</div>'
);
assert.isFalse(overflowHidden(vNode));
});

it('should return false for ancestor with "overflow:hidden; position:relative" and element with "position:fixed"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px; position: relative">' +
'<div id="target" style="margin-left: 100px; position: fixed">Hello world</div>' +
'</div>'
);
assert.isFalse(overflowHidden(vNode));
});

it('should return false for ancestor with "overflow:hidden" and element with "position:absolute"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px;">' +
'<div id="target" style="margin-left: 100px; position: absolute">Hello world</div>' +
'</div>'
);
assert.isFalse(overflowHidden(vNode));
});

it('should return true for ancestor with "overflow:hidden; position:relative" and element with "position:absolute"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px; position: relative">' +
'<div id="target" style="margin-left: 100px; position: absolute">Hello world</div>' +
'</div>'
);
assert.isTrue(overflowHidden(vNode));
});

it('should return true for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:relative"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px;">' +
'<div style="position: relative">' +
'<div id="target" style="margin-left: 100px; position: absolute">Hello world</div>' +
'</div>' +
'</div>'
);
assert.isTrue(overflowHidden(vNode));
});

it('should return true for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:sticky"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px;">' +
'<div style="position: sticky">' +
'<div id="target" style="margin-left: 100px; position: absolute">Hello world</div>' +
'</div>' +
'</div>'
);
assert.isTrue(overflowHidden(vNode));
});

it('should return false for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:absolute"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px;">' +
'<div style="position: absolute">' +
'<div id="target" style="margin-left: 100px; position: absolute">Hello world</div>' +
'</div>' +
'</div>'
);
assert.isFalse(overflowHidden(vNode));
});

it('should return false for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor in-between uses "position:fixed"', () => {
var vNode = queryFixture(
'<div style="overflow: hidden; width: 50px;">' +
'<div style="position: fixed">' +
'<div id="target" style="margin-left: 100px; position: absolute">Hello world</div>' +
'</div>' +
'</div>'
);
assert.isFalse(overflowHidden(vNode));
});

it('should return false for ancestor with "overflow:hidden" and element with "position:absolute" if ancestor of overflow node uses position other than static', () => {
var vNode = queryFixture(
'<div style="position: relative">' +
'<div style="overflow: hidden; width: 50px;">' +
'<div id="target" style="margin-left: 100px; position: absolute">Hello world</div>' +
'</div>' +
'</div>'
);
assert.isFalse(overflowHidden(vNode));
});
});

describe('clipHidden', () => {
Expand Down

0 comments on commit 2940f6e

Please sign in to comment.