If you weren’t aware, there is a new paradigm for web development on the horizon. Under development are various web browser specifications under the banner of “web components”. Web components aim to standardize what most mainstream JavaScript frameworks are already doing — provide a set of tools allowing the developer to create new HTML element-like classes implementing custom application logic. These components can potentially expose APIs through HTML properties, just like real elements, and be reused in many applications, finally solving the problem of code re-use on the front-end.
If you also weren’t aware, this process has been very painful and slow, with disagreements on standards, clunky APIs, and poor performance slowing adoption. One of the top Google results for a “web components” search, for me anyway, is this post titled The Broken Promise of Web Components. The Google Polymer framework attempts to put a band-aid on this by providing an idealized version of the web components API, and shoehorning it to work in all modern browsers. They have had some success, but even their main developer evangelist admits that the broken promise guy kind of has a point.
So where does that leave us front-end developers or dabblers? I have sung the praises of Polymer in the past and I still think it offers the ideal web development experience, although it may not be the right solution for most new web applications. To really get a feel for where the underlying technology is at, we should implement a web component in VanillaJS.
The <blink>
tag was a beloved design element in the late 90s and early 2000s. The native tag, available in most browsers, would cause whatever was enclosed in it to hide and show itself on an interval, so that the user would notice it. For example, one might add a promotion to one’s online store that flashed “SALE” in bright red serif text. This noble tag has been so maligned that it has fallen far out of fashion, even causing the normally stoic Mozilla Developer Network to publish perhaps the most hostile documentation page ever. Like Dr. Frankenstein, we aim to raise it from the dead, consequences be damned.
To create a custom element in ES6, we will first create a class that extends the built-in HTMLElement
class. Then, we can register it with the browser using the customElements
API.
class XBlink extends HTMLElement {
}
customElements.define('x-blink', XBlink);
Why x-blink
? Well, custom elements must have a dash in their name, so their names do not collide with built-in elements that have no dashes. As a result, most custom elements have some kind of prefix tacked on so they can still use simple one-word tag names. The prefix x-
is also commonly used for non-standard HTTP headers to distinguish them. Here is where we wonder aloud why adoption of this standard has been slow.
Our element in this form does and shows nothing. In order for it to display its children, we must attach a slot. The slot API lets us define where content placed inside our element should be displayed in our internal or “shadow” DOM tree. The blink tag has no structure of its own, so the internal DOM will only contain what we put in the slot. To add it we first have to attach a shadow DOM, which we declare as open for some reason, and create the slot element.
class XBlink extends HTMLElement {
constructor() {super();this.attachShadow({ mode: 'open' }).appendChild(document.createElement('slot'));}}
customElements.define('x-blink', XBlink);
Now, if we include this script in our page and use the tag, we should see the text displayed normally.
<x-blink>I'll blink if I like</x-blink>
Now to actually make it blink. Custom elements have reserved method names for “lifecycle callbacks”, simply meaning the developer can write functions to be run at certain key moments, mostly when the element is created or destroyed. When our element is created, we want to dispatch an interval function which will show and hide the contents of the tag. To avoid having the tag re-size itself when hiding, we will modify the opacity property to accomplish this.
connectedCallback() {
this.style.opacity = '1';this.interval = setInterval(() => {this.style.opacity = this.style.opacity === '1' ? '0' : '1'}, 500);}
disconnectedCallback() {clearInterval(this.interval);}
Now, our tag is blinking! We have brought back an old friend from the early days of the web. However, we are not using the full power of web components yet. For our component to be re-usable, it should expose an API so that it can be configured by a user without having to be re-written. The blink speed of 500ms could be a exposed in an HTML property, then the user could set it to blink faster or slower, depending on their UX requirements.
To do this, we can simply call this.getAttribute
from inside of our connectedCallback
, just as we would with an object instance of HTMLElement
, which we inherited from. Let’s set a default of 500ms if we don’t find that attribute, but use it if we do. HTML attributes are always strings, so we will attempt to parse it, not because we have to, but to avoid silly type coercion errors.
connectedCallback() {this.speed = parseInt(this.getAttribute('speed'));this.speed = isNaN(this.speed) ? 500 : this.speed;this.style.opacity = '1';
this.interval = setInterval(() => {this.style.opacity = this.style.opacity === '1' ? '0' : '1'}, this.speed);}
And that’s it! We’ve created a custom element, configurable through attributes, based entirely on the emerging browser standard. You can see why a framework like Polymer would want to add a templating syntax and data binding engine on top of this in order to actually build web applications. Are web components the end-all, be-all solution for re-usable browser code? Who knows. Right now they look about as good as the blink tag we resurrected, but beauty is in the eye of the beholder.
If you’d like to use x-blink
to make your own web page beautiful, the full example code is available on CodePen.