It’s Web Component Time – Making a Clock Using Web Components

Web Components are a way to approach application development on the web using reusable, encapsulated components. Although it is still currently under reviewed by W3C, with the help of polyfills, you can begin using them today for your app. Here are some main benefits:

  1. Encapsulation of styling within a component – you no longer have to worry about a minor CSS bug-fix leaking to other parts of your app
  2. DOM abstraction – instead of thinking the markup of your application in DIV and SPAN, you can now think of your DOM using custom tags that make sense semantically.
  3. High reusability – Since your HTML/CSS/JS are encapsulated, it is easy to recycle your components even across different projects

Timeless-Piece

GitHub    Demo

To demo how all of these works, I am going to create a clock using the Web Components standards. Before we dive into codes, let’s break our a clock down into smaller sub-components:

  • Clock Face – no functionality
  • Hour Hand – rotate to a certain degree based on hour and minutes of the moment
  • Minute Hand – rotate to a certain degree based on minutes of the moment
  • Second Hand – rotate to a certain degree based on seconds of the moment

You can see that each sub-component has a specific simple functional. We have almost already laid out exactly how we are going to build this. Let’s dive right in!

Timeless-Face

Link

We are going to start coding in a HTML file called timeless-face.html, and will be using the <template> tag. Template tag is a new element in HTML5. It allows one to create an inert chunk of DOM to be used later in the app. The content in a template is not rendered until it is appended to the DOM. So here is what our mark up will look like:

<template id="timeless-face">
    <style>
        .....CSS goes here.....
    </style>
    <div class="face">
         <div class="mark">XII</div>
         <div class="mark">I</div>
        .... and all the way to ....
         <div class="mark">X</div>
         <div class="mark">XI</div>
    </div>
</template>

Our markup will have one div(‘.face’) wrapped around 12 other divs(‘.mark’). We will style our div.face like this:

.face {
     width: 100%;
     height: 100%;
     border-radius: 50%;
     font-family: Oranienbaum;
     box-shadow: 0px 0px 5px black;
 }

and our div.mark like this:

.mark {
    position: absolute;
    width: 20%;
    height: 100%;
    text-align: center;
    left: 0;
    right: 0;
    margin: 0 auto 0 auto;
    font-size: 36px;
    letter-spacing: -5px;
    text-shadow: 0px 0px 1px rgba(0,0,0,0.4);
 }

And for every nth-elemnt of div.mark, we will rotate it by (n-1) * 30deg:

.mark:nth-child(2) {
    transform: rotate(30deg);
}

.mark:nth-child(3) {
    transform: rotate(60deg);
}

       ......

.mark:nth-child(12) {
    transform: rotate(330deg);
}

This will produce a circular clock face stretched to 100% of it’s container element.

Screen Shot 2015-12-02 at 1.25.51 AM

And then below our template tag, we will need to register our custom element using document.registerElement.

<script>
    (function() {
        var timelessFaceProto = Object.create(HTMLElement.prototype);
        var temp = document.currentScript.ownerDocument.querySelector('#timeless-face');
 
        timelessFaceProto.createdCallback = function() {
            var clone = document.importNode(temp.content, true);
            this.createShadowRoot().appendChild(clone);
        };

        document.registerElement('timeless-face', {prototype: timelessFaceProto});
    })();
</script>

First, we want to create a prototype object for our custom-element called timelessFaceProto. Then, we will set the createdCallback (to be invoked on creation of the element) of this prototype to do the following:

  1. Clone nodes in the template.
  2. Create a shadowRoot inside the main custom-element.
  3. Append cloned nodes into the shadowRoot.

Last, we will use document.registerElement to register a custom element called ‘timeless-face’, with the timelessFaceProto prototype. Next up, the second hand.

Timeless-Second

Link

The mark up for the second hand is just a simple div, styled as following:

.hand {
    position: absolute;
    display: inline-block;
    top: 5%;
    height: 45%;
    left: 0;
    right: 0;
    margin: 0 auto;
    width: 0.5%;
    background-color: red;
    box-shadow: 0px 0px 1px black;
    border-top-left-radius: 25%;
    border-top-right-radius: 25%;
    transform-origin: bottom;
 }

Then, we will follow the same pattern from previous example to register the custom element, but this time, we are adding two methods:

timelessSecondProto.attributeChangedCallback = function(name, oldVal, newVal) {
    if (name === 'second') {
        this.setSecond(newVal);
    }
};

timelessSecondProto.setSecond = function(seconds) {
    var deg = ((seconds % 60) / 60) * 360;
    this.hand.style.transform = 'rotate(' + deg + 'deg)';
};

The setSecond method will rotate the second hand to a certain degree based on the number of seconds you pass in. The attributeChangedCallback will call setSecond everytime you change an attribute on the element.

Timeless-Minute and Timeless-Hour

These two sub-components follow Timeless-Second’s pattern. Try to implement them on your own. Look here and here for code in GitHub.

Bring Them Together – Timeless-Piece

Link

So now, you can import all of the sub-components you created and put them in timeless-piece.html.

<link rel="import" href="./timeless-face.html">
<link rel="import" href="./timeless-hour.html">
<link rel="import" href="./timeless-minute.html">
<link rel="import" href="./timeless-second.html">

The <link> tag allows you to import HTML files into your current html file. After that, the template of your main component is simply:

<template id="timeless-piece">
    <style>
       /* :host reference the custom-element itself */
       :host {
            display: inline-block;
            position: relative;
        }
    </style>
    <timeless-face></timeless-face>
    <timeless-hour></timeless-hour>
    <timeless-minute></timeless-minute>
    <timeless-second></timeless-second>
</template>

And then create our prototypes:

timelessPieceProto.createdCallback = function() {
    var clone = document.importNode(temp.content, true);
    this.createShadowRoot().appendChild(clone);
    this.hour = this.shadowRoot.querySelector('timeless-hour');
    this.minute = this.shadowRoot.querySelector('timeless-minute');
    this.second = this.shadowRoot.querySelector('timeless-second');
    this.tick();
};

timelessPieceProto.tick = function() {
    var now = new Date();
    var hours = now.getHours();
    var minutes = now.getMinutes();
    var seconds = now.getSeconds();
    this.hour.setAttribute('hour', hours);
    this.hour.setAttribute('minute', minutes);
    this.minute.setAttribute('minute', minutes);
    this.second.setAttribute('second', seconds);
    setTimeout(this.tick.bind(this), 1000);
};

here we are registering methods to update attributes for each hand once every second. Because of the attributeChangedCallback in each of our sub-components, the rotation of each hand will be updated whenever we set an attribute of the hand.

Go to the demo page, open up the console and try messing around with different CSS changes. Such as putting this on body:

* {
    border: 1px solid blue;
}

Normally this would put a blue border on every single element on the page, but not the case here because the shadowDOM encapsulate styles within a component!

Web Components are tools that allow us to write clean, reusable code when developing for the web. I strongly urge everyone to start leveraging this new standard and push the web forward.

Happy Hacking!