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;
}