JavaScript30 - Day 20

| Comments

Day 20 of #javascript30 is about speech recognition in the browser. It is safe to say I had a ton of fun and that I got completely carried away.

SpeechRecognition

The browser's interface for speech recognition is pretty easy to work with. I'm amazed at how good it is at guessing what you say. Granted - it will completely mishear from time to time, but so do I sometimes!

Speech recognition language

As always when working with web stuff, my initial reaction to the speech recognition was: oh, that's cool, I bet it only works in English and in the US just like recipes, address forms and all that. But more and more - I am proven wrong on the web. It made me happy that I can set the language to Danish even though only about 5 million people speak it. Here is how you set the language:

const recognition = new SpeechRecognition();
// Set the language.
recognition.lang = 'da-DK ';

Simply set the lang property to a new language code. The language code is an ISO language code. A list of language codes can be found here.

Listening for results

You listen for speech input with the result event. The event variable will have a ton of info, but you can pull the string out like this by making the result an array and running map on it a couple of times:

recognition.addEventListener('result', e => {
    const transcript = Array.from(e.results)
        .map(result => result[0])
        .map(result => result.transcript)
        .join('');
    // Update stuff with the transcript string.
    if (e.results[0].isFinal) {
            // The user paused.
    }
});

Putting it all together.

For now speech recognition only works in Chrome. I made a page that can use voice commands to change background color. Go check it out! Make sure you use Chrome for this. It doesn't work in Firefox.

It supports Danish, Spanish, and English. It was so much fun to play around with. I really feel like my taking the time to do #javascript30 is starting to come back. I feel so much more confident and I write nicer code much faster now.


JavaScript30 - Day 19

| Comments

Day 19 of #javascript30 is messing around with the webcam for a photo booth and doing cool effects by manipulating the RGB channels.

Webcam with JavaScript

Is so easy! You call getUserMedia() on the mediaDevices singleton to get a promise. Like this for instance:

const video = document.querySelector('.player');
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
  .then(localMediaStream => {
    console.log(localMediaStream);
    video.src = window.URL.createObjectURL(localMediaStream);
    video.play();
  })
  .catch(err => {
    console.error('Ut oh', err);
  });

I had no idea that image manipulation was as simple as it is. There are 3 color channels in an RGB[A] image plus an alpha channel. I'm probably using the wrong words for these things, because I know very little about it. But go look at Pixel manipulation with canvas if you want to know more about how to change colors and make cool effects on images with JavaScript.

toDataURL()

Calling toDataURL() on a canvas element will give you the canvas content as a data url that you can either display or let the user download (or both!). Use the download attribute on the <a> tag to allow download.

const data = canvas.toDataURL('image/jpeg');
const link = document.createElement('a');
link.href = data;
link.setAttribute('download', 'smiling-face');
// Use the data for the image.
link.innerHTML = `<img src="${data}" alt="Smiling face" />`;

This was so cool. Interacting with an amazing piece of software like the browser is is so much fun. It's amazing what you can do with so little code! I really liked the CSS from the assignment that made my little webcam snaps look like polaroids:

Photo bootd


JavaScript30 - Day 18

| Comments

Day 18 of #javascript30 is about adding up playing times for videos using reduce(), some math, and a data attribute.

More compact code

I tend to prefer readable code over terse code, but I think I am starting to really like the one- or two-liners you can make with JavaScript. This assignment could be solved with just reduce() and some math after the reduce(). Here is code that goes trough all items with a time data attribute and adds up the seconds:

const lis = Array.from(document.querySelectorAll('[data-time]'));
const totalSeconds = lis.reduce((total, li) => {
  const [minutes, seconds] = li.dataset.time.split(':').map(i => parseInt(i, 10));
  return total + (minutes * 60) + seconds;
}, 0);

That is actually pretty neat I think. Much better than a bunch of loops like I would have done it in the past. An interesting side effect from doing all this thorough learning of JavaScript is that I get "inspiration" that I use in PHP everyday now. I have started using filter(), map(), reduce(), and sort() more in PHP and it makes for better code there too.

"Casting" with parseInt() and parseFloat()

In JavaScript the string concatenation operator is '+'. So it's not easy for JS to know what you want if you say this for instance:

console.log(23 + '01');

Do you mean "2301" or "24"? JS will assume the first, so you have to "cast" your variable to something that is a number. You might have noticed in the code snippet above that I pass a second argument to parseInt(). This is because if you have numbers that start with a '0', then parseInt assumes that it is either octal or decimal (this depends on the browser) if you don't specify the 'radix'. A radix is the mathematical numeral system eg. '10' like our ten-based. This is a perfect source of errors, so always pass the second parameter to parseInt.

I could have used parseFloat() that does not take the radix argument but this is a good gotcha and I like being precise.


JavaScript30 - Day 17

| Comments

Day 17 of #javascript30 is simply sorting an array of band names, but without considering articles like 'the', 'a' or 'an'.

I felt good about this one. I am starting to feel quite at home with JavaScript's array functions. I solved it without watching the video first and with very little code:

const bands = [
    'Another Band Name', 
    'An Amalgam', 
    'The Frosty Shred', 
    'Buffalo Of Arrow', 
    'The Thrash Paranoia', 
    'During Temperature', 
    'A During Temperature', 
    'The Drug Meeting'
    ];
const regex = new RegExp(/^(The|An|A)\ /, 'i');
bands.sort((a, b) => a.replace(regex, '') > b.replace(regex, '') ? 1 : -1);
document.querySelector('#bands').innerHTML = bands.map(band => `<li>${band}</li>`).join('');

Because sort() mutates the array, I needed very few lines of codes. I have linked to it before, but here is a list of array functions that mutate the array. It is important to understand, and nifty to know!


JavaScript30 - Day 16

| Comments

Day 16 of #javascript30 is having fun with a little bit of math and some CSS.

This lesson creates some text and when you hover over it, the text shadow follows the mouse. I learned some good practices when dealing with math for stuff like this. When calculating x and y position of the mouse on the mousemove event, then use the event.target.offsetLeft and event.target.offsetTop instead of event.offsetX and event.offsetY if the item you are listening on (this) is not the same as event.target.

Destructuring assignment

I already wrote something about the destructuring assignment and I said there that I didn't like the syntax and that I had yet to see a place where it would make sense. I think I found a variation that does now.

This is actually quite elegant, I think. The e is an event.

let { offsetX: x, offsetY: y} = e;

I now have two variables: x and y with the offset values from the event. Maybe I will have to reconsider my dislike of the destructuring assignment


JavaScript30 - Day 15

| Comments

Day 15 of #javascript30 is about localStorage and event delegation.

LocalStorage

The Window.localStorage a simple key-value store that lives in the browser. It is pr. site and has a nice and simple interface. Keep in mind that both the key and the value should be a string, so often times the value is made into json if it is an object.

The methods on the Storage object can be found here, but I'll just list the most important ones:

That's it! Nice and simple way to talk to a key-value store. Oh, there is also Storage.clear() for clearing the whole storehouse.

Event Delegation

Is basically putting an event listener on a parent element to avoid having to attach listeners when new items are added dynamically. When you want to have listeners on something that doesn't exist yet or maybe just instead of attaching 1000 listeners you can put the listener on the parent element. When the parent element is clicked for instance, you then look at the event to see if an item you want to listen for was the target.

function myEventHandler(e) {
  if (!e.target.matches('input')) {
    // Not what we are listening for, so return.
    return;
  }
  // An input element was clicked. Do what needs to be done.
}

preventDefault() confusion

This has had me confused. There is an Event.preventDefault in vanilla JS and then jQuery has event.preventDefault() with the exact same name. Just something to keep in mind to avoid confusion.


JavaScript30 - Day 14

| Comments

Day 14 of #javascript30 is understanding reference vs copy on variables.

This is where so many programmers make mistakes. Not just in JavaScript, but in most programming languages. Understanding — as in really understanding this is super important.

JavaScript and PHP are almost the same when it comes to refrence vs copy, with the exception of arrays. JavaScript assigns them by reference, and PHP doesn't. Strings, numbers and booleans are copies. Everything else (I think?) is reference.

Cloning arrays

The problem is that we can overwrite the original array when we edit the array that is pointing to it. Here is an example with tapirs, because they always make the best examples:

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Make an array with not just New World tapirs.
const allTapirs = newWorldTapirTypes;
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// There is now a wrong tapir in the new world types.
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Here is how to get a copy instead of a reference: Use slice():

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Use slice with no args
const allTapirs = newWorldTapirTypes.slice();
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Use concat():

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Use slice with no args
const allTapirs = [].concat(newWorldTapirTypes);
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Or use ES6's spread

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Use slice with no args
const allTapirs = [...newWorldTapirTypes];
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Cloning objects

Same problem:

const tapir = {
  name: 'Timmy',
  age: 4,
  type: "Baird's",
  home: "Los Angeles"
}

const tapir2 = tapir;
tapir2.home = 'Copenhagen';

console.dir(tapir);
console.dir(tapir2);
// Taprir 1 does not live in Copenhagen!
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Copenhagen' }
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Copenhagen' }

How to go about this?

Use Object.assign

const tapir = {
  name: 'Timmy',
  age: 4,
  type: "Baird's",
  home: "Los Angeles"
}

const tapir2 = Object.assign({}, tapir, { home: "Copenhagen" });
tapir2.home = 'Copenhagen';

console.dir(tapir);
console.dir(tapir2);
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Los Angeles' }
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Copenhagen' }

Note that that way of "cloning" is not recursive. If you need that, then first think about why you need that. There might be a better way around it. But if you really need it — then use this "poor man's clone":

const tapir = {
  name: 'Timmy',
  age: 4,
  type: "Baird's",
  home: {
    country: 'USA',
    city: 'Los Angeles'
  }
}

const tapir2 = JSON.parse(JSON.stringify(tapir));

tapir2.home = { country: "Denmark", city: "Copenhagen" };

console.dir(tapir);
console.dir(tapir2);
// { name: 'Timmy',
//  age: 4,
//  type: 'Baird\'s',
//   home: { country: 'USA', city: 'Los Angeles' } }
// { name: 'Timmy',
//  age: 4,
//  type: 'Baird\'s',
//  home: { country: 'Denmark', city: 'Copenhagen' } }

JavaScript30 - Day 13

| Comments

Day 13 of #javascript30 is a how to get those swishy images come fly in from the sides of the page.

Like Wes, I am not a big fan of animations that do this, but I still get requests for stuff like this from time to time. So I guess it is good to know.

In the excercise, a function is called through a debounce function on scroll. I have used a function like that in the past with underscore.js and it is a really good idea if you don't want to slam the browser with stuff on scroll.

window.scrollY

The window.scrollY property gives you the number of pixels that has been scrolled down. I like to think it of it as "number of pixels that i have scrolled out of view from the top". Note that it may or may not be an integer. Modern browsers will give subpixel values.

HTMLElement.offsetTop

The HTMLElement.offsetTop property will give you the number of pixels from the element you are on to the closest parent element that is relatively positioned. This is a little tricky and it really is just easiest to console.log the value and see for yourself before you start doing math.

Math with the two properties could be like the example below, where we find the position on the page of the middle of an element.

  const slideInAt = (window.scrollY + window.innerHeight) - image.height / 2;

JavaScript30 - Day 12

| Comments

Day 12 of #javascript30 is having fun with konami codes or whatever you want to be your secret code.

This lesson didn't introduce things that surprised me, but it had a very nice and precise use of .splice() that illustrates how it mutates the array instead of returning a new one. It is basically practicing what we learned in lesson 7 only it is slicing the array backwards. And you never become too good of a programmer to see examples using arrays.

Also. Many points for using the legendary Cornify!


JavaScript30 - Day 11

| Comments

Day 11 of #javascript30 is dealing with controlling a video player.

Like with the <audio> element, I was amazed at how simple the interaction with the player is in the <video> element. I learned how to control volume, skipping and the progress bar. Neat! Once again it blew my mind how many events you can listen for. Here is a list of media events and listening on them instead of trying to keep your own state for example the play button makes a ton of sense.

Flexbox was used in the lesson to control the controls and the super handy flex-basis was used for the progress bar in percent. Simple and elegant.

For the sliders for volume and playback rate, the function used in the lesson used the name of the event it was listening for to set the value. Sorta like this:

video[this.name] = this.value;

Where this is the event. The name of the events in this case was either playbackRate or volume, so it the could also be written like this:

if (this.name === 'volume') {
    video.volume = this.value;
}
else if (this.name === 'playbackRate') {
    video.volume = this.value;
}

.. but the first piece of code is much easier to use and understand once you get over the initial confusion thinking — wait.. Is video an array?. Obviously this will only work if the event names and the property names are the same.