For the last few weeks, I've been complaining about the shadow DOM. As much as I think it's an anti-pattern, there are some times where the shadow DOM can be situationally useful.
Today, I wanted to show you how you can mix-and-match the light DOM and the shadow DOM in a single Web Component.
Let's dig in!
Our Web Component
Let's imagine we have a <count-up>
custom element…
When our Web Component JavaScript instantiates, it renders into a <button>
, and a [role="status"]
element that displays how many times the <button>
has been clicked…
<count-up> <button>Count up!</button> <div role="status">Clicked 42 times</div> </count-up>
With everything in the light DOM, we can easily style our elements from a global stylesheet.
If you have default button
styles in your CSS, you don't even need to do anything special here. The Web Component will just pick up those global styles.
button { background-color: rebeccapurple; border: 0; border-radius: 0.25em; color: #fff; padding: 0.5em 1em; }
So why would we want the shadow DOM at all?
Your code can get broken in unpredictable ways
Imagine for a moment that someone does something silly, like set [role="status"]
elements to display: none
…
[role="status"] { display: none; }
Now, our Web Component is basically useless.
Users can click it as many times as they want. The component will keep track of it, and update the text. But the user will never see it.
I'm not sure this is a real concern in most apps, but I could see it being the kind of thing that could happen with third-party embedded widgets.
Here, encapsulating just the stuff you don't want users to mess with in the shadow DOM might be a way to prevent that from happening.
The light DOM-only version
First, let's look at how the light-DOM only version of this Web Component works.
We define our count-up
custom element, and pass in a class
that extends
the HTMLElement
…
customElements.define('count-up', class extends HTMLElement { // ... });
Inside the constructor()
, we'll define a count
attribute to keep track of how many times the button is clicked.
constructor () { // Always call super first in constructor super(); // Set attributes this.count = 0; }
Next, we'll create a button
element, assign it to a property, and give it some text. We'll also create a div
, give a role
of status
, and give it some text.
Then, we'll .append()
both of them inside the custom element.
constructor () { // Always call super first in constructor super(); // Set attributes this.count = 0; // Create the button this.btn = document.createElement('button'); this.btn.textContent = 'Count up!'; // Create the announcement text this.announce = document.createElement('div'); this.announce.setAttribute('role', 'status'); this.announce.textContent = `Clicked ${this.count} times`; // Append HTML into the DOM this.append(this.btn); this.append(this.announce); }
Finally, we'll add a click
event listener to the button
, and use the handleEvent()
method as our built-in event handler.
constructor () { // Always call super first in constructor super(); // Set attributes this.count = 0; // ... // Event listener this.btn.addEventListener('click', this); }
Inside the handleEvent()
method, we'll increase the count
by 1
, then update the displayed text.
handleEvent () { this.count++; this.announce.textContent = `Clicked ${this.count} times`; }
Here's a working demo.
Using the Shadow DOM for select elements
We can use the shadow DOM for just the [role="status"]
element.
To do that, we'll first attachShadow()
to create our shadow root
…
constructor () { // Always call super first in constructor super(); // Set attributes this.count = 0; // Creates a shadow root this.root = this.attachShadow({mode: 'closed'}); // ... }
When we go to .append()
our announce
element, we'll first add a <slot>
to the shadow root
.
Then, we'll append the announce
element into the root
instead.
With the <slot>
, our <button>
would not be displayed. With it, it shows up in the light DOM.
constructor () { // Always call super first in constructor super(); // Set attributes this.count = 0; // Creates a shadow root this.root = this.attachShadow({mode: 'closed'}); // ... // Append HTML into the DOM this.append(this.btn); this.root.innerHTML = `<slot></slot>`; this.root.append(this.announce); // Event listener this.btn.addEventListener('click', this); }
Here's another demo.
You'll notice that even with the [role="status"]
element set to display: none
, everything still works correctly.
Cheers,
Chris
Want to share this with others or read it later? View it in a browser.
0 Komentar untuk "[Go Make Things] Combining light DOM and shadow DOM in a Web Component"