Skip to content

Back to Snippets

isCssStackingContext()

This function checks whether the specified DOM element, or one of its pseudo-elements (::before, ::after) is a CSS stacking context. If it's a stacking context, it returns the reason why, along with the z-index value.

/*
Checks whether a DOM Element, or an associated pseudo-elements, is a CSS stacking context and returns the reason why that's the case.
*/


function isCssStackingContext(el, pseudo) {
if (el === document.documentElement) {
return ['doc'];
}

const s = getComputedStyle(el, pseudo);

if (s.zIndex !== 'auto') {
if (s.position === 'absolute' || s.position === 'relative') {
return [`position: ${s.position}`, s.zIndex];
}
const ps = getComputedStyle(el.parentNode);
if (ps.display === 'flex') {
return ['flex-child', s.zIndex];
}
if (ps.display === 'grid') {
return ['grid-child', s.zIndex];
}
}

if (['sticky', '-webkit-sticky', 'fixed'].includes(s.position)) {
return [`position: ${s.position}`, s.zIndex];
}

if (s.opacity !== '1') {
return [`opacity: ${s.opacity}`, s.zIndex];
}

if (s.mixBlendMode !== 'normal') {
return [`mix-blend-mode: ${s.mixBlendMode}`, s.zIndex];
}

if (s.transform !== 'none') {
return [`transform: ${s.transform}`, s.zIndex];
}

if (s.filter !== 'none') {
return [`filter: ${s.filter}`, s.zIndex];
}

if (s.perspective !== 'none') {
return [`perspective: ${s.perspective}`, s.zIndex];
}

if (s.clipPath !== 'none') {
return [`clip-path: ${s.clipPath}`, s.zIndex];
}

if (CSS.supports('mask', 'none') && s.mask !== 'none') {
return [`mask: ${s.mask}`, s.zIndex];
}

if (CSS.supports('mask-image', 'none') && s.maskImage !== 'none') {
return [`mask-image: ${s.maskImage}`, s.zIndex];
}

if (CSS.supports('mask-border', 'none') && s.maskBorder !== 'none') {
return [`mask-border: ${s.maskBorder}`, s.zIndex];
}

if (s.isolation === 'isolate') {
return [`isolation: isolate`, s.zIndex];
}

if (s.webkitOverflowScrolling === 'touch') {
return ['-webkit-overflow-scrolling: touch', s.zIndex];
}

const wc = s.willChange.split(',').map(it => it.trim());
const wck = ['opacity', 'mix-blend-mode', 'transform', 'filter', 'perspective', 'clip-path', 'mask', 'mask-image', 'mask-border', 'isolation'].find(k => wc.includes(k));
if (wck) {
return [`will-change: ${wck}`, s.zIndex];
}

const sc = s.contain.split(/\s+/).map(it => it.trim());
const sck = ['content', 'strict', 'paint', 'layout'].find(k => sc.includes(k));
if (sck) {
return [`contain: ${sck}`, s.zIndex];
}

if (s.containerType === 'size' || s.containerType === 'inline-size') {
return [`container-type: ${s.containerType}`, s.zIndex];
}

return false;
}