I have a project where I'm using the shadow DOM natively (not through a polyfill). I'd like to detect if a given element
is contained within a shadow DOM or a light DOM.
I've looked through all of the properties on the elements, but there don't seem to be any which vary based on the type of DOM an element is in.
How can I determine if an element is part of a shadow DOM or a light DOM?
Here is an example of what is considered "shadow DOM" and "light DOM" for the purpose of this question.
(light root) • Document (light) • HTML (light) | • BODY (light) | • DIV (shadow root) | • ShadowRoot (shadow) | • DIV (shadow) | • IFRAME (light root) | • Document (light) | • HTML (light) | | • BODY (light) | | • DIV (shadow root) | | • ShadowRoot (shadow) | | • DIV (none) | • [Unattached DIV of second Document] (none) • [Unattached DIV of first Document]
<!doctype html> <title> isInShadow() test document - can not run in Stack Exchange's sandbox </title> <iframe src="about:blank"></iframe> <script> function isInShadow(element) { // TODO } function test() { // (light root) • Document // (light) • HTML var html = document.documentElement; console.assert(isInShadow(html) === false); // (light) | • BODY var body = document.body; console.assert(isInShadow(body) === false); // (light) | • DIV var div = document.createElement('div'); body.appendChild(div); console.assert(isInShadow(div) === false); // (shadow root) | • ShadowRoot var divShadow = div.createShadowRoot(); var shadowDiv = document.createElement('div'); divShadow.appendChild(shadowDiv); // (shadow) | • DIV console.assert(isInShadow(shadowDiv) === true); // (shadow) | • IFRAME var iframe = document.querySelector('iframe'); shadowDiv.appendChild(iframe); console.assert(isInShadow(iframe) === true); // (light root) | • Document var iframeDocument = iframe.contentWindow.document; // (light) | • HTML var iframeHtml = iframeDocument.documentElement; console.assert(isInShadow(iframeHtml) === false); // (light) | | • BODY var iframeBody = iframeDocument.body; // console.assert(isInShadow(iframeHtml) === false); // (light) | | • DIV var iframeDiv = iframeDocument.createElement('div'); iframeBody.appendChild(iframeDiv); console.assert(isInShadow(iframeDiv) === false); // (shadow root) | | • ShadowRoot var iframeDivShadow = iframeDiv.createShadowRoot(); // (shadow) | | • DIV var iframeDivShadowDiv = iframeDocument.createElement('div'); iframeDivShadow.appendChild(iframeDivShadowDiv); console.assert(isInShadow(iframeDivShadowDiv) === true); // (none) | • [Unattached DIV of second Document] var iframeUnattached = iframeDocument.createElement('div'); console.assert(Boolean(isInShadow(iframeUnattached)) === false); // (none) • [Unattached DIV of first Document] var rootUnattached = document.createElement('div'); console.assert(Boolean(isInShadow(rootUnattached)) === false); } onload = function main() { console.group('Testing'); try { test(); console.log('Testing complete.'); } finally { console.groupEnd(); } } </script>
To identify Shadow DOM:Open Developer tools (press the shortcut keys Fn+F12). On the Elements tab, expand the <body> element and the first element inside the <body> element and notice the #shadow-root line.
Create a new project. Go to File > New > Project. Here, we name the project Shadow DOM Testing. In the demo website, navigate to the search bar, right-click > Inspect.
Accessing Elements by ID The easiest way to access a single element in the DOM is by its unique ID. You can get an element by ID with the getElementById() method of the document object. In the Console, get the element and assign it to the demoId variable.
Shadow DOM allows hidden DOM trees to be attached to elements in the regular DOM tree — this shadow DOM tree starts with a shadow root, underneath which you can attach any element, in the same way as the normal DOM.
If you call a ShadowRoot's toString()
method, it will return "[object ShadowRoot]"
. According to this fact, here's my approach:
function isInShadow(node) { var parent = (node && node.parentNode); while(parent) { if(parent.toString() === "[object ShadowRoot]") { return true; } parent = parent.parentNode; } return false; }
EDIT
Jeremy Banks suggests an approach in another style of looping. This approach is a little different from mine: it also checks the passed node itself, which I didn't do.
function isInShadow(node) { for (; node; node = node.parentNode) { if (node.toString() === "[object ShadowRoot]") { return true; } } return false; }
function isInShadow(node) { for (; node; node = node.parentNode) { if (node.toString() === "[object ShadowRoot]") { return true; } } return false; } console.group('Testing'); var lightElement = document.querySelector('div'); console.assert(isInShadow(lightElement) === false); var shadowChild = document.createElement('div'); lightElement.createShadowRoot().appendChild(shadowChild); console.assert(isInShadow(shadowChild) === true); var orphanedElement = document.createElement('div'); console.assert(isInShadow(orphanedElement) === false); var orphanedShadowChild = document.createElement('div'); orphanedElement.createShadowRoot().appendChild(orphanedShadowChild); console.assert(isInShadow(orphanedShadowChild) === true); var fragmentChild = document.createElement('div'); document.createDocumentFragment().appendChild(fragmentChild); console.assert(isInShadow(fragmentChild) === false); console.log('Complete.'); console.groupEnd();
<div></div>
You can check if an element has a shadow parent like this:
function hasShadowParent(element) { while(element.parentNode && (element = element.parentNode)){ if(element instanceof ShadowRoot){ return true; } } return false; }
This uses instanceof
over .toString()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With