Javascript30
April 01, 2021
This is my first big Vue project I’m building.
Repo: Javascipt30
Lessons: “Javascript30” by Wes Bos
01 - Javascript Drum Kit:
Topics Covered:
- key events
- playing audio
- listening for transition end events
- transition CSS
<script>
function playSound(e) {
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`)
const key = document.querySelector(`.key[data-key="${e.keyCode}"]`)
// if letter's key doesn't have an audio track associated, do nothing
if (!audio) return
// if you hit a key repeatedly, this will rewind it
// vs waiting untiil the full sounds plays to play it again
audio.currentTime = 0
audio.play()
// add class to key
key.classList.add("playing")
}
// the function to remove the css class and change styling
function removeTransition(e) {
if (e.propertyName !== "transform") return
this.classList.remove("playing")
}
// we select all items with key
const keys = document.querySelectorAll(".key")
// each key gets an event listener added called "transitionend"
// when it ends, we remove that event listener
keys.forEach(key => key.addEventListener("transitionend", removeTransition))
// the actual key event, when press, run the playSound function
window.addEventListener("keydown", playSound)
</script>
02 - CSS + JS CLock:
Topics Covered:
- transform & transition CSS properties
Date()
API
<script>
const secondHand = document.querySelector(".second-hand")
const minsHand = document.querySelector(".min-hand")
const hourHand = document.querySelector(".hour-hand")
function setDate() {
// get current real date & time
const now = new Date()
// gets the current second (in real life)
const seconds = now.getSeconds()
// converts the second (in time) to a degree number in a circle
// we add back 90 degrees to offset the initial 90 degrees we set in our CSS transform
const secondsDegrees = (seconds / 60) * 360 + 90
// transform the selected class item (second hand in this instance) by dynamic variable in degrees
secondHand.style.transform = `rotate(${secondsDegrees}deg)`
// repeat for minutes
const mins = now.getMinutes()
const minsDegrees = (mins / 60) * 360 + (seconds / 60) * 6 + 90
minsHand.style.transform = `rotate(${minsDegrees}deg)`
// repeat for hours
const hour = now.getHours()
const hourDegrees = (hour / 12) * 360 + (mins / 60) * 30 + 90
hourHand.style.transform = `rotate(${hourDegrees}deg)`
}
setInterval(setDate, 1000)
</script>
03 - CSS Variables and JS:
Topics Covered:
- CSS variables (non-SASS)
querySelectorAll
&setProperty
change
vsmousemove
events
<script>
// querySelector returns a "NodeList"
// NodeList prototype's have forEach property
// arrays have forEach already but now, we don't have to convert our NL to an array
// it's native in NodeList now
const inputs = document.querySelectorAll(".controls input")
// when its called,
function handleUpdate() {
// get the 'data-sizing' custom attribute we add
// in this case it's "px"
const suffix = this.dataset.sizing || ""
// set the "name" variable property to the input's value plus the suffix (px)
document.documentElement.style.setProperty(
`--${this.name}`,
this.value + suffix
)
}
// loop over each input
// listen for chagne event
// when its called, call handleUpdate function
// -------------------------------------------------
// THIS: when you lift your finger off the input (i.e. its done moving)
// and thus a value is selected, that value is our value
inputs.forEach(input => input.addEventListener("change", handleUpdate))
// THIS: when the value changes AT ALL as we're sliding around from left to right
inputs.forEach(input => input.addEventListener("mousemove", handleUpdate))
</script>
04 - Array Cardio 1:
Topics Covered:
filter()
map()
sort()
reduce()
console.table()
<script>
// 1. Filter the list of inventors for those who were born in the 1500's
const fifteen = inventors.filter(
inventor => inventor.year >= 1500 && inventor.year <= 1599
)
// 2. Give us an array of the inventors first and last names
const fullNames = inventors.map(
inventor => `${inventor.first} ${inventor.last}`
)
// 3. Sort the inventors by birthdate, oldest to youngest
const ordered = inventors.sort((a, b) => (a.year > b.year ? 1 : -1))
// 4. How many years did all the inventors live all together?
const totalYears = inventors.reduce((total, inventor) => {
return total + (inventor.passed - inventor.year)
}, 0)
// 5. Sort the inventors by years lived
const oldest = inventors.sort(function(a, b) {
const last = a.passed - a.year
const next = b.passed - b.years
return last > next ? 1 : -1
})
// 6. create a list of Boulevards in Paris that contain 'de' anywhere in the name
// https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris
const category = document.querySelector(".mw-category")
// <div class="mw-category">...</div>
const links = Array.from(category.querySelectorAll("a"))
// (39) [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]
const de = links
.map(link => link.textContent)
.filter(streetName => streetName.includes("de"))
// (12) ["Boulevard de l'Amiral-Bruix", "Boulevard des Capucines", "Boulevard de la Chapelle", "Boulevard de Clichy", "Boulevard de l'Hôpital", "Boulevard des Italiens", "Boulevard de la Madeleine", "Boulevard de Magenta", "Boulevard Marguerite-de-Rochechouart", "Boulevard de Sébastopol", "Boulevard de Strasbourg", "Boulevard de la Zone"]
// 7. Sort the people alphabetically by last name
const alpha = people.sort(function(last, next) {
const [aLastName, aFirstName] = last.split(", ")
const [bLastName, bFirstName] = next.split(", ")
return aLastName > bLastName ? 1 : -1
})
// 8. Reduce Exercise: Sum up the instances of each of these
const transportation = data.reduce(function(obj, item) {
if (!obj[item]) {
obj[item] = 0
}
obj[item]++
return obj
}, {})
</script>
05 - Flex Panels Image Gallery:
Topics Covered:
- Nested Flexbox containers
transitionY
transform property
<script>
// select all panels
const panels = document.querySelectorAll(".panel")
// add an open class
function toggleOpen() {
this.classList.toggle("open")
}
// add an open-active class to any event with property named "flex" (flex or flex-grow)
function toggleActive(e) {
if (e.propertyName.includes("flex")) {
this.classList.toggle("open-active")
}
}
// when clicked, go find this function and run it
// we don't do toggleOpen() because that will run on page load
panels.forEach(panel => panel.addEventListener("click", toggleOpen))
panels.forEach(panel => panel.addEventListener("transitionend", toggleActive))
</script>
06 - Ajax Type Ahead:
We’re push our data
array to our empty cities array we initialized so we’ll have an array within an array if we just pass it as cities.push(data)
but if we use the ES6 spread operator (...
) we’re just passing the data from one array into another. So with cities.push(...data)
we won’t have nested arrays.
Topics Covered:
RegExp
Objectfetch
with Vanilla JS
We hit the endpoint, which returns a Promise. THEN, we take that response, which returns a Promise, and parse the json with .json()
. THEN, we take that data, as data
, and we can console.log()
it to see it.
fetch("http://example.com/movies.json")
.then(response => response.json())
.then(data => console.log(data))
<script>
const endpoint =
"https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json"
const cities = []
// we're push our data array to our empty cities array we initialized so we'll have an array within an array if we just pass it as `cities.push(data)` but if we use the ES6 spread operator `...` we're just passing the _*data*_ from one array into another. so with `cities.push(...data)` we won't have nested arrays
fetch(endpoint).then(blob => blob.json().then(data => cities.push(...data)))
function findMatches(wordToMatch, cities) {
return cities.filter(place => {
// g = global, i = insensitive (lower & uppercase)
const regex = new RegExp(wordToMatch, "gi")
return place.city.match(regex) || place.state.match(regex)
})
}
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
function displayMatches() {
const matchArray = findMatches(this.value, cities)
const html = matchArray
.map(place => {
const regex = new RegExp(this.value, "gi")
const cityName = place.city.replace(
regex,
`<span class="hl">${this.value}</span>`
)
const stateName = place.state.replace(
regex,
`<span class="hl">${this.value}</span>`
)
return `
<li>
<span class="name">${cityName}, ${stateName}</span>
<span class="population">${numberWithCommas(place.population)}</span>
</li>
`
})
.join("")
suggestions.innerHTML = html
}
const searchInput = document.querySelector(".search")
const suggestions = document.querySelector(".suggestions")
searchInput.addEventListener("change", displayMatches)
searchInput.addEventListener("keyup", displayMatches)
</script>
07 - Array Cardio 2:
Topics Covered:
.some()
.every()
.find()
.findIndex()
<script>
// ## Array Cardio Day 2
const people = [
{ name: "Wes", year: 1988 },
{ name: "Kait", year: 1986 },
{ name: "Irv", year: 1970 },
{ name: "Lux", year: 2015 },
]
const comments = [
{ text: "Love this!", id: 523423 },
{ text: "Super good", id: 823423 },
{ text: "You are the best", id: 2039842 },
{ text: "Ramen is my fav food ever", id: 123523 },
{ text: "Nice Nice Nice!", id: 542328 },
]
// Some and Every Checks
// Array.prototype.some() // is at least one person 19 or older?
// const isAdult = people.some(function (person) {
const isAdult = people.some(person => {
const currentYear = new Date().getFullYear()
return currentYear - person.year >= 19
})
console.log({ isAdult })
// Array.prototype.every() // is everyone 19 or older?
const allAdults = people.every(person => {
const currentYear = new Date().getFullYear()
return currentYear - person.year >= 19
})
console.log({ allAdults })
// Array.prototype.find()
// Find is like filter, but instead returns just the one you are looking for
// find the comment with the ID of 823423
const comment = comments.find(comment => comment.id == 823423)
console.log(comment)
// Array.prototype.findIndex()
// Find the comment with this ID, delete the comment with the ID of 823423
const index = comments.findIndex(comment => comment.id == 823423)
console.log(index)
console.table(comments)
comments.splice(index, 1)
console.table(comments)
</script>
08 - HTML5 Canvas:
Topics Covered:
<script></script>
09 - 14 DevTools Tricks:
Topics Covered:
<script></script>
10 - Hold Shift to Check Multiple Checkboxes:
Topics Covered:
<script></script>
11 - HTML5 Video Player:
Topics Covered:
<script></script>
12 - Key Sequence Detection (KONAMI CODE):
Topics Covered:
<script></script>
13 - Slide In on Scroll:
Topics Covered:
<script></script>
14 - Objects & Arrays - Reference vs. Copy:
Topics Covered:
<script></script>
15 - LocalStorage and Event Delegation:
Topics Covered:
<script></script>
16 - CSS Text Shadow Mouse Move Effect:
Topics Covered:
<script></script>
17 - Sorting Band Names w/o articles:
Topics Covered:
<script></script>
18 - Tally String Times with Reduce:
Topics Covered:
<script></script>
19 - Unreal Webcam:
Topics Covered:
<script></script>
20 - Native Speech Recognition:
Topics Covered:
<script></script>
21 - Geolocation-based Speedometer & Compass:
Topics Covered:
<script></script>
22 - Follow Along Links:
Topics Covered:
<script></script>
23 - Speech Synthesis:
Topics Covered:
<script></script>
24 - Sticky Nav:
Topics Covered:
<script></script>
25 - Event Capture, Propogation, Bubbling, and Once:
Topics Covered:
<script></script>
26 - Stripe Follow Along Dropdown:
Topics Covered:
<script></script>
27 - Click & Drag to Scroll:
Topics Covered:
<script></script>
28 - Video Speed Controller UI:
Topics Covered:
<script></script>
29 - Countdown Clock:
Topics Covered:
<script></script>
30 - Whack-A-Mole Game:
Topics Covered:
<script></script>
Written by Christian Turner
Follow me on Twitter