In line with doing things on my own, yesterday I built a keyboard with vanilla Js. I watched a tutorial by WebDevSimplified on Youtube and saw that he used arrays for the white and black keys, but I had to break through the pattern of copying and decided to go with storing all my piano's information in an array of objects. I decided not to create the keys in my html, but instead to display them dynamically with javascript.
My html file is basically empty with just a placeholder div for the piano:
<div class="piano"></div>
The piano looks like this:
https://liz-piano.netlify.app/
Nothing too fancy because I will be changing the css later.
Now, for the Javascript. I created an array of objects like I stated earlier to implement this in an OOP fashion. A snippet of this can be found here:
const data = [
{
keyColor: "white",
keyNote: "C",
keyAudio: "notes/C.mp3",
keyLetter: "z",
},
{
keyColor: "black",
keyNote: "Db",
keyAudio: "notes/Db.mp3",
keyLetter: "s",
},
{
keyColor: "white",
keyNote: "D",
keyAudio: "notes/D.mp3",
keyLetter: "x",
},
etc]
So there are 4 keys;keyColor, keyNote, keyAudio and keyLetter
I wrote a function for the data to be able to create the keys with a createPianoKey() function:
function createPianoKey(item) {
const pianoKey = document.createElement("div");
const { keyColor, keyNote, keyAudio, keyLetter } = item;
const styles = ["key", keyColor];
pianoKey.classList.add(...styles);
piano.appendChild(pianoKey);
}
I destructured each of the keys in my array of objects and stored them in the 'item'
I was looking for a way to add multiple styles to the keys and since I have .white and .black styles, at first I tried to add them in my array of styles, but for some reason the .black styles ended up overlapping. On reddit, someone suggested that I match the .key style with the keyColor key and this worked fine. That was a cool trick to know.
I wanted the keys to reflect the keyLetters since there was nothing in the html and so I added the following line in my fucntion:
pianoKey.innerHTML =
<span style='font-weight:bold;font-size:30px; color:teal; '>${keyLetter}</span>
;
Next was thinking of how to add the sounds upon clicking the keys. I did the following:
const sound = new Audio();
pianoKey.addEventListener("click", () => {
sound.src = keyAudio;
sound.play();
pianoKey.classList.add("active");
setTimeout(function () {
pianoKey.classList.remove("active");
}, 1000);
});
I consoled.logged keyAudio, so I knew that each key was correctly associated with the audionote for the key. The setTimeout was necessary to remove the active class upon clicking the key.
I also wanted the user to be able to utilize their keyboard so I needed to write a keydown event listener function:
document.addEventListener("keydown", (e) => {
if (e.key == keyLetter) {
sound.src = keyAudio;
sound.play();
pianoKey.classList.add("active");
setTimeout(function () {
pianoKey.classList.remove("active");
}, 1000);
}
The keydown takes an event and if you write a simple console.log like :
console.log(key=${e.key},code=${e.code}
); you will be able to see the keycode of every key you have pressed. Also, I could not add the eventListener directly on the pianoKey as I did with the clickListener because this event listener listens to the activities on the window. I hope this is a correct way of explaining things.
So, my entire function block looks like this:
data.forEach(createPianoKey);
function createPianoKey(item) {
const pianoKey = document.createElement("div");
const { keyColor, keyNote, keyAudio, keyLetter } = item;
const styles = ["key", keyColor];
pianoKey.classList.add(...styles);
piano.appendChild(pianoKey);
pianoKey.innerHTML = `${keyLetter}`;
const sound = new Audio();
pianoKey.addEventListener("click", () => {
sound.src = keyAudio;
sound.play();
pianoKey.classList.add("active");
setTimeout(function () {
pianoKey.classList.remove("active");
}, 1000);
});
document.addEventListener("keydown", (e) => {
if (e.key == keyLetter) {
sound.src = keyAudio;
sound.play();
pianoKey.classList.add("active");
setTimeout(function () {
pianoKey.classList.remove("active");
}, 1000);
}
});
}
I have some repeated code which is visible in the following lines:
sound.src = keyAudio;
sound.play();
pianoKey.classList.add("active");
setTimeout(function () {
pianoKey.classList.remove("active");
}, 1000);
I'm wondering if I could get some help refactoring those lines in a new function and also some suggestions on what could be done better using OOP principles.
please note I'm aware the css is not optimized for phones unless you auto-rotate. I have it in mind to code a 3d version of this, as I'm more focused on the Javascript bit.
Thanks!