[005.4] Lighting your Christmas tree with Choo

Decorate, animate, and provide a soundtrack for a Christmas tree

Subscribe now

Lighting your Christmas tree with Choo [12.22.2017]

Welcome to the christmas special episode. This week we learned about Bankai and the Choo Framework.

Creating our Choo project

Now, we will use Choo and make a Christmas tree. This will be a Choo project. The project name will be christmas-tree.

npx create-choo-app christmas-tree

Choo project created.

Getting Started

To use external CSS files in Choo we will use Sheetify.

In our index.js file we will import the CSS we need. Choo comes with tachyons, it's a good CSS framework. However, for now we will use our own css. We have created a css folder to put our css files in.

...
css("tachyons");
css("./css/normalize.css");
css("./css/container.css");
css("./css/tree.css");
css("./css/white-decoration.css");
css("./css/ornaments.css");
...

Alexander from our team did this CSS, thanks Alex! I’ll explain it.

We will use normalize, and we already have applied a boilerplate template adding a container class.

This will be our final result:

Let's start by defining the parts of the xmas-tree:

We can see that an xmas-tree has three or four branches, the trunk, and some ornaments and lights. There're many approaches to draw with css. So, I'll explain what we are going to do:

We're going to go from top to bottom. Create the top branch without any ornaments, then we'll use it as base for the other branches and the trunk. Lastly, we'll do the ornaments and animate the lights.

So, our markup now looks like this

<body>
  <div class="container">
    <div class="tree">
      <div class="star"></div>
      <div class="trunk"></div>
      <div class="ornaments -top">
        <div class="ornament -red -on"></div>
        <div class="ornament -yellow -off"></div>
      </div>
      <div class="ornaments -middle">
        <div class="ornament -purple -off"></div>
        <div class="ornament -blue -on"></div>
      </div>
      <div class="ornaments -bottom">
        <div class="ornament -yellow -on"></div>
        <div class="ornament -red -off"></div>
      </div>
      <div class="white-decoration -top">
      </div>
      <div class="white-decoration -middle -one">
      </div>
      <div class="white-decoration -middle -two">
      </div>
      <div class="white-decoration -bottom">
      </div>
    </div>
  </div>
</body>

.tree is our base, to do the bigger branches we'll use before and after pseudo-elements. The other classes are pretty self-explanatory, but it's important to keep in mind that all the clases with - are modifiers, .-top, .-middle and .-bottom are vertically position references.

In .ornament each class is a reference to its color. The classes òn and off are references to the ornament state

To do the base branch, we're going to take advantage of a CSS behavior. Lets see what happens if the we do a square with all the borders using different colors.

  .tree {
    border-style: solid;
    border-width: 6rem 6em 6em 6em;
    border-color: blue red green gold;
    position: absolute;
  }

The borders form triangles. So, using that behavior from CSS, we can make triangles of different sizes. The trick is to use border-color: transparent;. So, let's make our first branch.

  .tree {
    border-radius: 35%;
    border-style: solid;
    border-width: 6rem 6em 8em 6em;
    border-color: transparent transparent green transparent;
    height: 0;
    left: 35%;
    position: absolute;
    width: 0;
  }

The order for border-color is top, right, bottom, left. We want the bottom triangle. So, we set transparent color in the rest. Height and Width are 0, because those are simulated by the border-width. Lastly, we set border-radius: 35%; to make it look less statical.

Now, we just have to apply the style to our before and after pseudo elements, and change the size.

  .tree {
    border-radius: 35%;
    border-style: solid;
    border-width: 6rem 6em 8em 6em;
    border-color: transparent transparent green transparent;
    height: 0;
    left: 35%;
    position: absolute;
    width: 0;
  }

  .tree:before,
  .tree:after {
    border-radius: 40%;
    border-style: solid;
    border-color: transparent transparent green transparent;
    content: '';
    height: 0;
    position: absolute;
    width: 0;
  }

  .tree:before {
    border-width: 0 9em 9em 9em;
    top: 5rem;
    left: -9rem;
  }

  .tree:after {
    border-width: 0 12em 12em 12em;
    left: -12rem;
    top: 8rem;
  }

And now, this looks more like an xmas-tree. The important thing to keep in mind is that we are moving to the left a quantity of rem equal to the width of the pseudo element. This is for centering purposes. Now let's do the trunk, it’s basically a brown rectangle with a darker border.

  .trunk {
    border: .2rem solid #4f3222;
    left: -2rem;
    background: #7e4a35;
    height: 6rem;
    position: absolute;
    top: 18rem;
    width: 4rem;
  }

  .trunk:before,
  .trunk:after {
    background-color:#4f3222;
    content: '';
    height: 30%;
    width: .1rem;
    position: absolute;
  }

  .trunk:after {
    right: 25%;
    bottom: 25%;
  }

  .trunk:before {
    right: 65%;
    bottom: 10%;
  }

Nothing crazy. We did the trunk and the little line details at the middle were done with before and after pseudo elements.

Now, let's do the white decoration.

  .white-decoration {
      border: solid .3rem;
      border-color: white transparent transparent transparent;
  }

  .white-decoration.-top {
      border-radius: 50%/2rem 2rem 0 0;
      height: 5rem;
      left: -5.5rem;
      position: absolute;
      top: -.75rem;
      transform: rotate(160deg);
      width: 8rem;
  }

we basically do a white line, and use border-radius for the curve, it's important to notice theproperty value50%/2rem 2rem 0 0;, look what happens if we remove the50%`.

  .white-decoration.-top {
      border-radius: 2rem 2rem 0 0;
      ...
  }

See? Now the curve is not smooth. It distributes the border-radius across the entire element instead of just one part.

Now we're going to add the modifiers style to have different white lines in various places of the tree.


  .white-decoration.-middle.-one {
      border-radius: 50%/3rem 3rem 0 0;
      height: 5rem;
      left: -6.75rem;
      position: absolute;
      top: 4.7rem;
      transform: rotate(165deg);
      width: 10rem;
      z-index: 1;
  }

  .white-decoration.-middle.-two {
      border-radius: 50%/3rem 3rem 0 0;
      height: 5rem;
      left: -4rem;
      position: absolute;
      top: 5.5rem;
      transform: rotate(200deg);
      width: 12rem;
      z-index: 1;
  }

  .white-decoration.-bottom {
      border-radius: 50%/3rem 3rem 0 0;
      height: 5rem;
      left: -8rem;
      position: absolute;
      top: 10.5rem;
      transform: rotate(180deg);
      width: 16rem;
      z-index: 2;
  }

This is looking more like an xmas-tree now! Last thing we're going to do, is to add the ornaments/lights. Let's create a couple first, then the rest.

  .ornaments {
    position: absolute;
    width: 100%;
  }

  .ornaments.-top {
    top: 6rem;
  }

  .ornaments.-middle {
    top: 12rem;
    z-index: 1;
  }

  .ornaments.-bottom {
    top: 16rem;
    z-index: 2;
  }

  .ornaments > .ornament {
    border-radius: 50%;
    height: 1.5rem;
    position: relative;
    width: 1.5rem;
  }

  .ornaments > .ornament:before {
    background: black;
    content: '';
    height: .5rem;
    left: .7rem;
    position: absolute;
    width: .1rem;
    top: -.5rem;
  }

  .ornaments > .ornament.-red.-on {
    background-color: red;
  }

  .ornaments > .ornament.-red.-off {
    background-color: darkred;
  }

  .ornaments > .ornament.-yellow.-on {
    border: .2rem solid darkgoldenrod;
    background-color: yellow;
  }

  .ornaments > .ornament.-yellow.-off {
    border: .2rem solid darkgoldenrod;
    background-color: darkgoldenrod;
  }

First we position all the ornament containers, then we style the ornaments, they're circles of different colors, to emulate the flashing lights. We set a darker color to the class .off and a brighter color to the class on. Finally let's add the rest of the ornaments

  .ornaments > .ornament.-blue.-on {
    border: .2rem solid darkblue;
    background-color: blue;
  }

  .ornaments > .ornament.-blue.-off {
    border: .2rem solid darkblue;
    background-color: darkblue;
  }

  .ornaments > .ornament.-purple.-on {
    border: .2rem solid darkviolet;
    background-color: purple;
  }

  .ornaments > .ornament.-purple.-off {
    border: .2rem solid darkviolet;
    background-color: darkviolet;
  }

  .ornaments.-top > .ornament.-red {
    border: .2rem solid darkred;
    top: -2.3rem;
    right: -1rem;
  }

  .ornaments.-top > .ornament.-yellow {
    left: -2.5rem;
    top: -2.6rem;
  }

  .ornaments.-middle > .ornament.-purple {
    left: -4rem;
    top: -1rem;
  }

  .ornaments.-middle > .ornament.-blue {
    right: -1rem;
    top: -2.5rem;
  }

  .ornaments.-bottom > .ornament.-red {
    left: -6rem;
    top: -2.2rem;
  }

  .ornaments.-bottom > .ornament.-yellow {
    right: -3rem;
    top: .15rem;
  }

Boom! Our xmas-tree is completed.

Now, we would like to add a sound to our page. We will be using Choo audio. Let's install it as an npm package. I will download a free jingle from internet. Let me look for jingle bells.

vim index.js

We will have a function loadSounds that will be executed when the DOM loads. Then we can use the audio load event. Once it's loaded we play the audio.

function loadSounds(state, emitter, app) {
  emitter.on("DOMContentLoaded", function() {
    emitter.emit("audio:load", "assets/jingle_bell.wav");
    emitter.on("audio:load-complete", function() {
      emitter.emit("audio:play");
    });
  });
}

With this, once we load the page, it plays the sound! Sweet!

Turning on/Off the lights

We would like to turn on and off the lights with a button. How can we do that? We need to remove a modifier as a class from our lights. In this case, the classes -on and -off.

Our view will emit a function to do this. We have a store for clicks. This store will be changed to lights.

module.exports = store;

function store(state, emitter) {
  state.lights = true;

  emitter.on("DOMContentLoaded", function() {
    emitter.on("lights:toggle", function() {
      console.log("toggle");
      state.lights = !state.lights;
      emitter.emit(state.events.RENDER);
    });
  });
}

When we emit the action lights:toggle we will toggle the state, and re-render the component.

In our HTML component, we will have a button to toggle our state. In each light that contains -on or -off we will have a function to get the state and show on or off based on the state. Let's do that.

var html = require("choo/html");

var TITLE = "🚂🚋🚋";

module.exports = view;

function view(state, emit) {
  if (state.title !== TITLE) emit(state.events.DOMTITLECHANGE, TITLE);

  return html`
    <body>
      <div class="container">
        <button onclick=${() => emit("lights:toggle")}>
        toggle
        </button>
        <div class="tree">
          <div class="star"></div>
          <div class="trunk"></div>
          <div class="ornaments -top">
            <div class="ornament -red -${lightStatus(state, 1)}"></div>
            <div class="ornament -yellow -${lightStatus(state, 1)}"></div>
          </div>
          <div class="ornaments -middle">
            <div class="ornament -purple -${lightStatus(state, 1)}"></div>
            <div class="ornament -blue -${lightStatus(state, 1)}"></div>
          </div>
          <div class="ornaments -bottom">
            <div class="ornament -yellow -${lightStatus(state, 1)}"></div>
            <div class="ornament -red -${lightStatus(state, 1)}"></div>
          </div>
          <div class="white-decoration -top">
          </div>
          <div class="white-decoration -middle -one">
          </div>
          <div class="white-decoration -middle -two">
          </div>
          <div class="white-decoration -bottom">
          </div>
        </div>
    </div>
  </body>
  `;

  function lightStatus(state, lightNumber) {
    if (state.lights) {
      return "on";
    } else {
      return "off";
    }
  }
}

In our function lightStatus we have a lightNumber, that potentially we could use to turn on a specific light. This might be for the future.

Now we can see it working! We can toggle our lights and we also have the jingle. Sweet!

Deploying

Let’s deploy it with now. I will create an alias to our website, and I will deploy it.

Deployed. We can visit our website! Merry Christmas!

Summary

Today we learned how to draw an xmas-tree with flashing lights using CSS and JavaScript!

Resources