This week, we've looked at how Web Components are different from React, how to create your first Web Component, and how to add options to a Web Component.
Today, we're going to look at one of my favorite Web Component features: progressive enhancement.
Let's dig in!
A few different approaches
There are two different ways to progressively enhance a Web Component (there's probably more, but these are the two I use most often)…
- If the content only works with JavaScript, hide it, and only show it after the Web Component instantiates.
- If the content works without JavaScript, show a default HTML experience, and then layer in additional HTML and JavaScript-based interactivity after the Web Component instantiates.
In this article, we'll look at both approaches, and when and why to choose one approach over the other.
Approach 1. Hide the content
For this approach, let's look at the example we've been using all week, our <wc-count>
component that creates a counter button.
<wc-count> <button>Clicked 0 Times</button> </wc-count>
Here, the button does absolutely nothing without JavaScript. As a result, it probably makes sense to hide it entirely until the Web Component is ready.
There are a few ways to do that…
Using the [hidden]
attribute
One way to do that is by slapping a [hidden]
attribute on the element.
<wc-count hidden> <button>Clicked 0 Times</button> </wc-count>
Then, in our constructor()
method, we can remove the attribute when instantiating the component.
/** * The class constructor object */ constructor () { // Always call super first in constructor super(); // ... // Show the element this.removeAttribute('hidden'); }
Using CSS
The :defined
pseudo-class provides another way to detect when a custom element has been defined using just CSS instead of JavaScript.
We can combine it with the :not()
pseudo-class to hide our custom element until it's defined.
/* Hide the <wc-count> element until it's defined */ wc-count:not(:defined) { display: none; }
With a fallback message
It might be a good idea to load a fallback message while the content is loading.
Here, I've wrapped that content in a <wc-count-loading>
custom element, and added the [hidden]
attribute to the <button>
element instead.
<wc-count> <button hidden>Clicked 0 Times</button> <wc-count-loading>Loading...</wc-count-loading> </wc-count>
Then, in the constructor()
method, I remove the [hidden]
attribute from this.button
. I also use the Element.querySelector()
method to get the <wc-count-loading>
element, and the Element.remove()
method to remove it once the content is ready.
/** * The class constructor object */ constructor () { // Always call super first in constructor super(); // Instance properties this.button = this.querySelector('button'); // ... // Show the element this.button.removeAttribute('hidden'); this.querySelector('wc-count-loading')?.remove(); }
Approach 2. Layering in HTML and interactivity
This approach works great when you have content that stands on its own, but benefits from some added flourish or interactivity.
For example, let's imagine you're creating an accordion group, with different expand/collapse sections.
You can start this off with HTML that includes headings and content, like this…
<accordion-group> <h2>Why is Lil' Wayne hip-hop top 5 list?</h2> <div> <p>...</p> </div> <h2>How many spells could Merlin cast in a day?</h2> <div> <p>...</p> </div> </accordion-group>
When you instantiate your Web Component in the constructor()
method, you can then update the UI to hide or show elements as needed, inject additional HTML, add required attributes, and so on.
/** * Instantiate the Web Component */ constructor () { // Get all accordion headings let headings = this.querySelectorAll('h2'); // Update content for (let heading of headings) { // Get the matching content let content = heading.nextElementSibling; if (!content) continue; // Create a button, and copy heading content into it let btn = document.createElement('button'); btn.innerHTML = heading.innerHTML; // Wipe the heading content, and replace it with the button heading.innerHTML = ''; heading.append(btn); // Hide the content content.setAttribute('hidden', ''); // Add ARIA btn.setAttribute('aria-expanded', false); } }
You end up with HTML that looks something like this…
<accordion-group> <h2><button>Why is Lil' Wayne hip-hop top 5 list?</button></h2> <div hidden> <p>...</p> </div> <h2><button>How many spells could Merlin cast in a day?</button></h2> <div hidden> <p>...</p> </div> </accordion-group>
Which approach should you use?
I favor starting with base HTML and layering in interactivity whenever you can.
If I have content that simply does not work with HTML and has no base-level experience, I'll try to include a fallback message until the Web Component loads, but otherwise hide the content.
Like this? You can support my work by purchasing an annual membership.
Cheers,
Chris
Want to share this with others or read it later? View it in a browser.
0 Komentar untuk "[Go Make Things] Progressively enhancing a Web Component"