30 days of JavaScript - Part 4 of 5

November 20, 2019

Five step closer to finishing off this JS30 challenge. This is the second to last part: Day 21 through to 25.

As you might have noticed, we have a little bit of a delay. I have taken a break for vacation in Utah and Arizona over Thanksgiving weekend. But catching up now in order to finish it all off before Christmas! ❄️❄️❄️

Here we go:

Day 21: Geolocation based Speedometer and Compass

This lesson provides a few insights into working with Geolocation: 💥Longitude, Longitude 💥Heading - information that comes with Geolocation:

    💥 Location - how many degrees of North is the person currently at
    💥 Speed 

There is no codepen for this specific challenge as we are using the server (for the secure origin in order to be able to access Geolocation) and iOS simulator, but I do encourage you to checkout the video itself where Wes shows everything step by step.

When you run your simulator, you can go to Debug tab and choose location that will give you options to simulate various movements, such as a freeway ride or city run.

geolocation data

As always, we start with selecting some HTML elements to work with: arrow and speed. And we watch for the user’s position:

                    const speed = document.querySelector('.speed-value');

                    navigator.geolocation.watchPosition((data) => {
                        console.log(data);
                        speed.textContent = data.coords.speed;
                        arrow.transform = `rotate(${data.coords.heading}deg)`
                    });

In this way we are getting real time data and update our html for the speed element as well as css for the arrow element to rotate it depending on the current heading data.

Day 22: Follow Along Links

In this lesson we learn about the active link highlighting with a pill that on hover:

💥grows and shrinks depending on the link text's size
💥follows your mouse moves on the page

We start with the basic HTML for navigation:

html navigation

How does it actually work? We have span with a class of highlight that gets added to the DOM. When you hover over any element on the page it will figure out the width, height and where on the page that element is.

We need to get all the links on the page that trigger the event. We create a highlight element and add it to our document. We listen to mouseenter event on each of our links and run the function to highlight the link. If we log this, we will get this output in the console with all the links we hovered over:

script

Now we need to figure out how big and wide as well as where on the page the element is that we hovered over.

We use getBoundingClientRect() function that gives us all the information about where on the page our link lives.

script

And then we just apply it to the CSS inline styles with the values we get:

                highlight.style.width = `${coords.width}px`;
                highlight.style.height = `${coords.height}px`;
                highlight.style.transform = `translate(${coords.left}px, ${coords.top}px)`;

When you test the highlight behaviour at this point, you will notice your links are getting the CSS properties just as we wanted.

However, there is one gotcha here. If we scroll the page and try to highlight our active elements again, you will notice that although our highlight css shows up on the page, it doesn’t match the location of the element we are hovering over, it is off by the value of the scroll down.

So we can fix it by checking how far on the page we are scrolled down (window.scrollY), and then add that to the top and left coordinates of each item:

                const coords = {
                    width: linkCoords.width,
                    height: linkCoords.height,
                    top: linkCoords.top + window.scrollY,
                    left: linkCoords.left + window.scrollX
                };

You can check out the codePen entry I created for this lesson. Although it seems quite straightforward at first, it is easy to get tripped up with this one.

Day 23: Text to voice aka Speech Synthesis

This lesson is similar to what we learnt about Voice to Speech in video no. 20 of Part 3.

We are using SpeechSynthesisUtterance API

it represents a speech request. It contains the content the speech service should read and information about how to read it (e.g. language, pitch and volume

and Speech Synthesis of Web API:

is the controller interface for the speech service; this can be used to retrieve information about the synthesis voices available on the device, start and pause speech, and other commands besides.

My favourite part of this video is the second minute where Wes describes the features we are going to build (‘Heeeeellllooo’ in a very high pitch LOL 😜😜😜):

    💥 the voice 💥the pitch 💥the rate 💥the text area

html

We create a new instance of what a person is going to say:

                    const msg = new SpeechSynthesisUtterance();

And then we select a few HTML elements we are going to work with. It is worth to pay attention to how options have been selected, by their input’s name:

                    const options = document.querySelectorAll('[type="range"], [name="text"]');

On page load whatever is in the text area we want it to be default:

                    msg.text = document.querySelector('[name="text"]').value;

Speech Synthesis has a voiceschanged event listener available to us and when that happens we can populate the voices (this.getVoices) array for our dropdown.

html

There are a lot of different voices and languages available, so we want to just filter for the voices in English:

                    function populateVoices() {
                        voices = this.getVoices();
                        voicesDropdown.innerHTML = voices
                            .filter(voice => voice.lang.includes('en'))
                            .map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
                            .join('');
                    }

Speech Synthesis takes the utterance and recites it to us. In order to be able to pick a voice from the dropdown, we add change event listener on it and then compare the value from the dropdown to the values available in the voices array, and when they match, the voice is being set:

                    function setVoice() {
                        msg.voice = voices.find(voice => voice.name === this.value);
                    }

Next we want to be able to set the options (pitch, rate and text) to our chosen values. We listen to on change event, and then set the name to the changed values:

                    function setOption() {
                        console.log(this.name, this.value);
                        msg[this.name] = this.value;
                    }

And that is it!

Day 24: Sticky Nav

This is a great lesson that helps us understand how to create a navigation bar that is visible at all times when the user interacts with our website.

We are selecting the nav element from our HTML and listen to the scroll event on window. We need to figure out where is the top of the navigation bar (nav.offsetTop), when we scroll, we need to know how far did we scroll (window.scrollY), as soon as we hit the threshold of scrolling more than the top of the navigation, then we want to make sure to keep the navigation on screen by fixing it (making it sticky):

html

Full solution available on codepen.

We then need to add position: fixed to our navigation’s css in order to make it sticky when the body has a class of ‘.fixed-nav’.

You may find that after you made the navigation’s position fixed, the content seems to be jumpy the moment it is about to apply the css when the navigation is about to scroll out of the view. It happens, because the position fixed means the navigation no longer takes space in DOM, it floats on top of the browser. So the moment navigation no longer takes this space, the element underneath it in DOM jumps up to take that freed up space. In order to fix that we offset the space the nav would otherwise take by adding the padding equal to its hight:

                        document.body.style.paddingTop = nav.offsetHeight + 'px';

There is one more interesting part about this lesson. When you take a look at the finished version, you will notice that Logo element slides in from the left, when we reach the top of the navbar. Let’s take a look at the css that makes it happen:

css

The moment the body has the class of ‘.fixed-nav’ we set max-width of li.logo from 0 to a number bigger than its width. We add transition of 0.5s and voilà! It is important to do that on max-width, because with the regular width the animation from 0 to auto would not work.

Day 25: Event Capture, Propagation, Bubbling and Once

This is very important lesson of a few concepts that are related to the event listeners in browsers. We have three divs nested within each other. We select all of divs and loops over them, adding on ‘click’ event listener to each. We create a logText function that will fire off when a div is clicked.

Events bubble up

Then we click on the inner most div, number 3, and we notice that all of the divs got logged from the number 3 we clicked on through to number 1. We also in the same spirit clicked on the body, html tag, browser, tab in google chrome, and it keeps zooming out and out. This is so called bubbling. The browser will figure out what you clicked on, but will also trigger clicks all the way up the ladder too.

How does it work? When you click on something, the browser will do the capture. It goes from the top down, captures all these events and starts bubble up, triggering the events as we go up.

That’s why when declaring your event listeners, you have a third option of capture available. If you set capture to true, it won’t bubble up, but it will run the events from top to bottom instead. Running the function on the way down as opposed to all the way up.

Event stop propagation

It stops the propagation behaviour from happening. If we use that in our previous example, it will stop the propagation of bubbling up to the parents.

events

Once

If you set it to true, it will listen to an event once and then it will unbind itself. That can be helpful if we have a button that we only want for somebody to click on once, for example in store checkouts. We need to change once: true.

All my solutions are available on my CodePen, go and check it out in order to explore the context for some of these lessons in more detail.