Completing 30-day vanilla JS projects in 30 days — Part 4

Ruth Newman
9 min readJun 2, 2019

--

I have written three blogs so far on the first three weeks of a 30-day challenge to build a web application with vanilla JavaScript every day (Part One here, Part Two here and Part Three here). I have saved a Github repository with links to all 30 projects’ hosted websites and their own Github repositories, which can be found here.

In this final blog of the series, I will summarise the last 9 projects of the challenge, including becoming more familiar with HTML5 Canvas and game development, working with a couple more public APIs, and dealing with image, video, text and voice input and output. At the end, I will reflect on what I learnt overall from this challenge and where I want to take what I’ve learnt with me going forward.

Day 22: “Talk To Type” — https://ruthenewman.github.io/TalkToType/

This application was the almost the reverse of my Polly the Parrot application from week 3. Instead of taking user input in the form of text and outputting speech, this application takes user input in the form of speech and outputs text. The main application looks like a simple note taking application with a light bulb icon symbolising its potential for logging ideas generated by any particular user.

I started by saving a new instance of a Speech Recognition to a variable.

let recognition = new SpeechRecognition();

I added an event listener to the Speech Recognition and then created a new variable for a transcript generated from the result. The words were joined to form a string. The para variable (already set by traversing the DOM to select the correct spot on the page), sees its text content updated to reflect the transcript.

recognition.addEventListener('result', event => {  
const transcript = Array.from(event.results)
.map(result => result[0])
.map(result => result.transcript)
.join('')
para.textContent = transcript;
Like this for example!

I even added a little ‘easter egg’ to the application, wherein if a user says the word ‘light’, a light bulb icon appears instead of the word itself:

if(transcript.includes('light')){      
const lightScript = transcript.replace(/light/gi, '💡');
para.textContent = lightScript;
}

Voila!

This is easy to add to, with emojis and emoticons possible for all kinds of speech input, but that’s a larger project for another day

Day 23: “One Season Following Another”https://ruthenewman.github.io/OneSeasonFollowingAnother/

The next day in the week saw me look at another public API, the Sunrise Sunset API, which meant I needed a different name for my application designed to generate sunrise and sunset times based on a user’s location. Film or theatre aficionados might guess where my second choice came from instead.

That aside, the application employs the geolocation property of the window’s Navigator object to retrieve a user’s location, based on which I called either a getToday function with the latitude and longitude of the user’s location as the function’s arguments or a similar getTomorrow function.

todayButtonDiv.addEventListener('click', event => {      
navigator.geolocation.getCurrentPosition(position => {
long = position.coords.longitude;
lat = position.coords.latitude;
getToday(lat, long);
});
});

The getToday function fetches from an interpolated URL with the latitude and longitude of the user’s location and setting the date for today. The innerHTML of the wrapperDiv is updated to include the sunrise and sunset times, and the todayButtonDiv’s display is set to ‘none’, with the tomorrowButton’s display set to ‘block’:

function getToday(latitude, longitude) {  
fetch(`${baseURL}lat=${latitude}&lng=${longitude}&date=today`)
.then(response => response.json())
.then(data => {
let sunrise = data.results.sunrise.slice(0,4);
let sunset = data.results.sunset.slice(0,4);
wrapperDiv.innerHTML =
`<div class="today">
<h3>Sunrise time today: <span class="time">${sunrise} AM</span></h3>
<h3>Sunset time today: <span class="time">${sunset} PM</span></h3>
</div>`;
todayButtonDiv.style.display = 'none';
tomorrowButton.style.display = 'block';
})
}

Clicking on the ‘Get tomorrow’s sunrise and sunset times’ will update the site to reflect the marginally different times for the next day:

The days are still getting marginally longer!

Day 24: “Play Pacman”https://ruthenewman.github.io/PlayPacman/

I had an aborted attempt earlier in the challenge to make a snake game, and I knew from that experience that I needed to learn more about HTML5 Canvas if I wanted to explore game development with JavaScript.

Drawing to the canvas and moving items around it were key skills I was going to have to conquer, and I had seen simple versions of Pacman games were a common early project with HTML5 Canvas. I decided to have a go.

I set about creating a function that would initialise what my game would look like. This included a variable to build a map of the game, a nested array of numbers which would reflect which type of image to display to the canvas, and a pacman object which would reflect the starting coordinates. I set the score to 0 and called the drawGame function:

function initialise() {  
gameMap = [
[1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,4,4,4,4,4,1,4,4,4,4,4,1],
[1,4,1,1,1,4,1,4,1,1,1,4,1],
[1,4,1,4,4,4,4,4,4,4,1,4,1],
[1,4,4,4,1,1,2,1,1,4,4,4,1],
[1,4,1,4,4,4,4,4,4,4,1,4,1],
[1,4,1,1,4,4,1,4,4,1,1,4,1],
[1,4,4,4,4,4,1,4,4,4,4,4,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1],
];
pacman = {
x: 6,
y: 4
}
score = 0;
drawGame();
}

The draw game function iterated through the gameMap array and its nested array’s within it, using two for loops, and using a switch statement, depending on the case (i.e. which number), it added a div to the HTML with a class of wall, pacman, ground or coin.

function drawGame() {  
gameDiv.innerHTML = "";
for(let i = 0; i < gameMap.length; i++) {
for(let j = 0; j < gameMap[i].length; j++) {
switch (gameMap[i][j]) {
case 1:
gameDiv.innerHTML += '<div class="wall"></div>';
break;
case 2:
gameDiv.innerHTML += '<div class="pacman"></div>';
break;
case 3:
gameDiv.innerHTML += '<div class="ground"></div>';
break;
case 4:
gameDiv.innerHTML += '<div class="coin"></div>';
break;
}
}
}
}

The final game canvas looks like this, and is redrawn in response to a user’s interactions (pressing different arrow keys):

Day 25: “Retro Snake Game”https://ruthenewman.github.io/RetroSnakeGame/

I built on my HTML5 canvas experience by returning to snake the next day, albeit starting from scratch entirely.

For this, I started by drawing an empty black canvas for the snake to move around in:

context.fillStyle = "black";        
context.fillRect(0,0, canvas.width, canvas.height);

I also drew smaller green and red Rect’s to the canvas to reflect the snake and its target (an apple perhaps):

The snake needed to grow its tail as the user found its target, and the apple needed to move to a random new location at the same point:

if(appleX == playerX && appleY == playerY) {            
tail++;
appleX = Math.floor(Math.random() * tileCount);
appleY = Math.floor(Math.random() * tileCount);
}

Day 26: “TFL Times”https://github.com/RuthENewman/TFLTimes

I took a day’s break from game development to return to exploring different API’s, this time the Transport for London (or ‘TFL’ for those who also spend a lot of time here in London).

The site loads with a form for user to input their starting station and destination:

It adds below the form the journeys between the two destinations, including the start time, arrival time and duration:

data.journeys.forEach(journey => {        
showJourneysDiv.innerHTML +=
`<div class="journeyDetails" id=${journey.startDateTime}>
<h3 class="journeyDetailsHeader">Journey option</h3>
<h4 class="journeyDetailsHeading">
Date: <span>${journey.startDateTime.slice(8,10)}</span>
/ <span>${journey.startDateTime.slice(5,7)}</span>
/ <span>${journey.startDateTime.slice(0,4)}</span>
</h4>
<h4 class="journeyDetailsHeading">
Start time: <span>${journey.startDateTime.slice(11,16)}</span>
</h4>
<h4 class="journeyDetailsHeading">Arrival time:
<span>${journey.arrivalDateTime.slice(11,16)}</span>
</h4>
<h4 class="journeyDetailsHeading">Duration:
<span>${journey.duration} minutes</span></h4>
</div>`;

And the arrival point, departure point and duration of each of the legs for each of the journeys:

journey.legs.forEach(leg => {          
document.getElementById(`${journey.startDateTime}`).innerHTML +=
`<div class="leg">
<h5 class="legHeading">Leg</h5>
<h5>Departure point:
<span>${leg.departurePoint.commonName</span>
</h5>
<h5>Arrival point:
<span>${leg.arrivalPoint.commonName}</span>
</h5>
<h5>Duration:
<span>${leg.duration}</span> minutes
</h5>
<h5>Mode:
<span>${leg.mode.name}</span>
</h5>
<h5>Line:
<span>${leg.routeOptions[0].name}</h5>
</div>`;

Day 27: “Noir Et Blanc” https://ruthenewman.github.io/NoirEtBlanc/

I had worked a lot with text input, a little with sound input but less with image input, so this application is designed to render any image uploaded in black and white (hence the name). The main user interface looks as below:

input.addEventListener('change', event => {  handleFiles(input.files);}, false)

I also wanted to make sure to remove the initial image if another was uploaded:

img.onload = function() {      
if(displayImageDiv.children.length > 1) {
displayImageDiv.removeChild(displayImageDiv.children[0]); }

And to give an example of how this works with one of the images from this very blog:

Day 28: “Play Ping”https://ruthenewman.github.io/PlayPing/

I returned to game development with HTML5 Canvas, developing a ping game, common among early projects of game developers, and further practicing drawing to and moving around the canvas.

I drew paddles to the side of the canvas with their sizes depending on the level a user has chosen, as well as changing the speed of the ball’s movement:

easyLevelButton.addEventListener('click', event => {      
paddleHeight = 120;
paddle2Height = 80;
ballXSpeed = 10;
})
intermediateLevelButton.addEventListener('click', event => {
paddleHeight = 100;
paddle2Height = 80;
ballXSpeed = 15;
})

difficultLevelButton.addEventListener('click', event => {
paddleHeight = 50;
paddle2Height = 100;
ballXSpeed = 20;
})

A function which moves the ball around the canvas includes increasing the position of the ball by the speed set:

function move() {  
if(winningScreen) {
return;
}
computerPlays();
ballX += ballXSpeed;
ballY += ballYSpeed;

Moving the mouse moves the left paddle and the game works!

Day 29: “Retro Tetris Game”https://ruthenewman.github.io/RetroTetrisGame/

My final HTML5 canvas project of the whole challenge was a tetris game, requiring mapping out the individual pieces similarly to in the Pacman game, by using nested arrays to represent empty or coloured squares within each piece:

function createPiece(letter) {  
switch (letter) {
case 'T':
return [
[0,3,0],
[3,3,3],
[0,0,0],
];
case 'O':
return [
[4, 4],
[4, 4],
];
case 'L':
return [
[0,5,0],
[0,5,0],
[0,5,5],
];

Iterating through an array with a list of the colours sets the colour for each shape based on the number in the array:

Rotating and dropping the pieces is set in response to keyboard input for a user, depending on the key code:

document.addEventListener('keydown', event => {  
switch (event.keyCode) {
case 37:
playerMove(-1);
break;
case 39:
playerMove(+1);
break;
case 40:
playerDrop();
break;
case 38:
playerRotate(-1);
break;
case 32:
playerRotate(1);
break;
}
})

Day 30: “Helter Skelter Crazy Filters” — https://ruthenewman.github.io/HelterSkelterCrazyFilters/

This application deals with video input from the user, and adds different coloured filters, with the user able to select one and then ‘take a photo’, which can then be downloaded as an image file to the user’s computer.

function getVideo() {  
navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then(localMediaStream => {
video.srcObject = localMediaStream;
video.play();
})

Adding a coloured filter works by iterating through all the pixels and adding or decreasing the hex value:

function greenEffect(pixels) {  
for(let i = 0; i < pixels.data.length; i += 4) {
pixels.data[i + 0] = pixels.data[i + 0] - 50;
pixels.data[i + 1] = pixels.data[i + 1] + 50;
pixels.data[i + 2] = pixels.data[i + 2] * 0.5;
}
return pixels;
}

Overall this has been a great endeavour, and I feel I have learnt a huge amount from putting my skills to practice and building a range of different projects. After a period of time, generating new ideas became the most challenging part!

I have enjoyed exploring game development a little, but realise this is a huge area where there is much more to learn, so perhaps worthy of a challenge of its own.

I feel much more comfortable with JavaScript than I did at the beginning of the month, but I know there are whole stacks (e.g. MERN stack, MEAN stack) beyond vanilla JavaScript that I could and should explore more. I am particularly interested to turn my hand to the back end side of things with Node.JS given this has been a month of front end development. Whilst vanilla JavaScript is important to understand to fully understand the language, jQuery remains a popular way to use JavaScript (although I know there are strong opinions on its value and future), so I need to be equally comfortable with both.

Areas to look at next therefore are: 1) Projects using testing 2) Full Stack projects with Node/Express/MongoDB 3) More advanced game development, 4) JS projects with jQuery. I will continue to build and add further features and to my knowledge base and keep blogging about them!

--

--