Codementor Events

Defining custom element utilizing the Shadow DOM (Virtual DOM vs Shadow DOM)

Published Jul 12, 2018
Defining custom element utilizing the Shadow DOM (Virtual DOM vs Shadow DOM)

I was having a conversation with a colleague a couple days ago about JavaScript and different web frameworks. He was talking to me about React vs Angular vs Vue and kept using the term "Shadow DOM."

The context in which he was bringing it up made me believe he was meaning to say "Virtual DOM" so I inquired if he actually meant to say "Virtual DOM" or "Shadow DOM." His response was that he thought they were the same thing. This is an easy mistake, and ended up making for a really good conversation.

Now, to be clear, in this post, I'm assuming you understand what the DOM is. If not, here is an introduction. Once you have given that a read, you will see that our HTML page gets broken down into a tree-like structure that we as developers can manipulate.

Frameworks like React and Vue create an abstraction of the DOM, which is called the "Virtual DOM." The point of this abstraction is to minimize changes to the actual DOM, which can cause performance issues.

The shadow DOM is something different. The shadow DOM is a sub-tree structure that can only be seen from the element the tree stems from. Let's take a look at a pretty standard HTML doc...

<!DOCTYPE html>
<html>
  <head>
    <title>Shadow DOM Example</title>
  </head>
  <body>

    <div id="content">
        </div>

  </body>
</html>

What we are going to do is illustrate how the Shadow DOM can be used by creating a custom element. My dev alias is devhulk, so how about we create a devhulk element that looks something like this...

<dev-hulk></dev-hulk>

I will go ahead and link the full code project below so you can try it out and play around. For now, we are going to need a class that extends HTMLElement...

class DH extends HTMLElement {
  constructor() {
    	let shadow = this.attatchShadow({mode: 'open'})
        ...
    }
}

This is what we would use in whatever our entrypoint JavaScript file is. Now, I wanted to emphasize the above one line of code because that's how easy it is to attach a Shadow DOM on an element.

Now what we are going to do is attach other elements to the shadow, which is just done through the normal DOM JavaScript API. The awesome part here is that when we pull up our custom element in the browser, the elements we have added to the Shadow DOM are not recognized as normal DOM elements.

Maybe you are starting to see how you could get truly scoped styling by using the Shadow DOM instead of having to worry so much about CSS class names/ids etc. I would bet that's how Vue does its <style scoped> option as well for all of your Vue components. Just food for thought. Let's finish our custom element...

class DH extends HTMLElement {
  constructor() {
      super()
      let shadow = this.attachShadow({mode: 'open'})
      // Create spans
      let wrapper = document.createElement('span');
      wrapper.setAttribute('class','wrapper');
      let icon = document.createElement('span');
      icon.setAttribute('class','icon');
      icon.setAttribute('tabindex', 0);
      let info = document.createElement('span');
      info.setAttribute('class','info');

      // Insert icon
      let imgUrl;
      if(this.hasAttribute('img')) {
        imgUrl = this.getAttribute('img');
      } else {
        imgUrl = 'littlehulk.png';
      }
      let img = document.createElement('img');
      img.src = imgUrl;
      icon.appendChild(img);
      var style = document.createElement('style');
      style.textContent = `
      .wrapper {
        position: relative;
      }

      .info {
        font-size: 0.8rem;
        width: 200px;
        display: inline-block;
        border: 1px solid black;
        padding: 10px;
        background: white;
        border-radius: 10px;
        opacity: 0;
        transition: 0.6s all;
        position: absolute;
        bottom: 20px;
        left: 10px;
        z-index: 3;
      }

      img {
        width: 1.2rem;
      }

      .icon:hover + .info, .icon:focus + .info {
        opacity: 1;
      }`;
      shadow.appendChild(style);
      shadow.appendChild(wrapper);
      wrapper.appendChild(icon);
      wrapper.appendChild(info);

  }


}

module.exports = DH

Now this element doesn't have much practical use, but it's more so to illustrate the concept. Let's actually create our custom element in our main.js file now.

import DH from './dh.js'

customElements.define('dev-hulk', DH);

Now I can build and use my custom <dev-hulk/> element!!

<!DOCTYPE html>
<html>
  <head>
    <title>Shadow DOM Example</title>
  </head>
  <body>

    <div id="content">
        	<dev-hulk></dev-hulk>
        </div>

  </body>
</html>

If you've want to download the project and run it yourself, you will need pm2 and webpack installed.

git clone https://github.com/devhulk/shadow-dom.git
cd shadow-dom
npm i 
pm2 start pm2.json

Go to localhost:3000 and take a look. When you open the dev tools, you will see your custom element and the #shadow-root under it with its scoped styling and the elements that you defined.

Pretty cool! Hopefully this gave you a little bit of an understanding of what the Virtual DOM is vs the Shadow DOM. I also hope you got some value out of the Shadow DOM example and its piqued your curiosity to keep experimenting.

Happy coding!

Discover and read more posts from Gerald Yerden
get started