This article is part of the A New Vue On JavaScript30 series that explores re-implementing JavaScript30 projects using Vue. Before reading, its a good idea to have:
- Completed JavaScript30’s 01 - JavaScript Drum Kit project
- Read the A New Vue On JavaScript30 - Getting Started article
Key Vue Concepts
The following Vue concepts are discussed in this article:
-
v-for
directive - Class binding
- Event binding
01 - JavaScript Drum Kit
The first of the JavaScript30 projects walks through building a drum kit that binds key presses to playing drum sounds. This one is a lot fun and I ended up creating two different Vue implementations:
Lets take a look at the first version I created.
Vue Version 1
For the first approach, I decided to simply insert the code from the original project into their corresponding Vue locations:
- The HTML section fit inside the root
<div>
- The functions went into the
methods
section - The JavaScript that executed on page load was placed into the
mounted
function - The
data
,computed
, andwatch
sections were not needed so they were removed
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS Drum Kit</title>
<link rel="stylesheet" href="style.css">
<!-- Use Vue from a CDN -->
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<!-- Vue Root DOM Element -->
<div id="app">
<div class="keys">
<div data-key="65" class="key">
<kbd>A</kbd>
<span class="sound">clap</span>
</div>
<div data-key="83" class="key">
<kbd>S</kbd>
<span class="sound">hihat</span>
</div>
<div data-key="68" class="key">
<kbd>D</kbd>
<span class="sound">kick</span>
</div>
<div data-key="70" class="key">
<kbd>F</kbd>
<span class="sound">openhat</span>
</div>
<div data-key="71" class="key">
<kbd>G</kbd>
<span class="sound">boom</span>
</div>
<div data-key="72" class="key">
<kbd>H</kbd>
<span class="sound">ride</span>
</div>
<div data-key="74" class="key">
<kbd>J</kbd>
<span class="sound">snare</span>
</div>
<div data-key="75" class="key">
<kbd>K</kbd>
<span class="sound">tom</span>
</div>
<div data-key="76" class="key">
<kbd>L</kbd>
<span class="sound">tink</span>
</div>
</div>
<audio data-key="65" src="sounds/clap.wav"></audio>
<audio data-key="83" src="sounds/hihat.wav"></audio>
<audio data-key="68" src="sounds/kick.wav"></audio>
<audio data-key="70" src="sounds/openhat.wav"></audio>
<audio data-key="71" src="sounds/boom.wav"></audio>
<audio data-key="72" src="sounds/ride.wav"></audio>
<audio data-key="74" src="sounds/snare.wav"></audio>
<audio data-key="75" src="sounds/tom.wav"></audio>
<audio data-key="76" src="sounds/tink.wav"></audio>
</div>
<!-- Vue Instance Section -->
<script>
var app = new Vue({
el: '#app',
methods: {
removeTransition: function (e) {
if (e.propertyName !== 'transform') { return }
e.target.classList.remove('playing')
},
playSound: function (e) {
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`)
const key = document.querySelector(`div[data-key="${e.keyCode}"]`)
if (!audio) { return }
key.classList.add('playing')
audio.currentTime = 0
audio.play()
}
},
mounted: function () {
const keys = Array.from(document.querySelectorAll('.key'))
keys.forEach(key => key.addEventListener('transitionend', this.removeTransition))
window.addEventListener('keydown', this.playSound)
}
})
</script>
</body>
</html>
While this version works and was easy to make, I didn't feel as if it took advantage of Vue's features so I decided to iterate on it.
Vue Version 2
When looking at the HTML section of the JavaScript30 project, you can see a repetitive pattern for each key: keyCode, keyName, and soundName. By extracting those parts into a data structure, we can then make use of Vue's v-for
directive to loop it and create the same HTML. Lets first look at the data structure, an array of objects where each object represents a key:
data: {
keys: [
{keyCode: 65, keyName: "A", soundName: "clap", audio: new Audio("sounds/clap.wav"), isPlaying: false},
{keyCode: 83, keyName: "S", soundName: "hihat", audio: new Audio("sounds/hihat.wav"), isPlaying: false},
{keyCode: 68, keyName: "D", soundName: "kick", audio: new Audio("sounds/kick.wav"), isPlaying: false},
{keyCode: 70, keyName: "F", soundName: "openhat", audio: new Audio("sounds/openhat.wav"), isPlaying: false},
{keyCode: 71, keyName: "G", soundName: "boom", audio: new Audio("sounds/boom.wav"), isPlaying: false},
{keyCode: 72, keyName: "H", soundName: "ride", audio: new Audio("sounds/ride.wav"), isPlaying: false},
{keyCode: 74, keyName: "J", soundName: "snare", audio: new Audio("sounds/snare.wav"), isPlaying: false},
{keyCode: 75, keyName: "K", soundName: "tom", audio: new Audio("sounds/tom.wav"), isPlaying: false},
{keyCode: 76, keyName: "L", soundName: "tink", audio: new Audio("sounds/tink.wav"), isPlaying: false},
]
},
In addition to including the keyCode, keyName, and soundName, I also added an HTMLAudioElement for playing the audio and an isPlaying
flag. This allows us to remove the <audio>
elements and simplify the logic for adding and removing the playing
class which gets applied to the key div elements. If you are new to Vue, this next section may look a bit complex but I'll do my best to break it down.
<div id="app">
<div class="keys">
<div
v-for="key in keys"
:key="key.keyCode"
:class="[key.isPlaying ? 'playing' : '', 'key']"
@transitionend="removeTransition($event, key)"
>
<kbd>{{ key.keyName }}</kbd>
<span class="sound">{{ key.soundName }}</span>
</div>
</div>
</div>
As you can see the using the v-for
directive has shortened the HTML section significantly. Now we are able to loop the keys
array, placing its data values into their corresponding spots. Vue recommends setting the :key
attribute when using v-for
and even though its not necessary for this code I went ahead and did it anyway. For more information on the :key
attribute you can read about it here.
On the next line down I'm binding classes to the <div>
element. This will always apply the key
class, but will only apply the playing
class when key.isPlaying
is truthy.
Lastly in the loop, we set the transitionend
event to call the removeTransition()
method passing the $event
and key
objects. Setting the transitionend
event this way allows me to remove querySelectorAll
call and the addEventListener
loop from the mounted function.
Now all that is left are some small changes to the removeTransition
and playSound
methods control the key.isPlaying
flag and to play the audio from the keys
array.
Putting It All Together
By taking advantage of Vue's features I was able to make some nice improvements to the code. Looping the data structure instead of the repetitive HTML makes the code easier to maintain and expand in the future. New keys can be added by simply adding a new entry into the keys
array. If the HTML needs to change, it can be updated in one spot instead of each of the nine key <div>
elements. Some additional improvements were:
- Removal of all direct JavaScript DOM queries & manipulations
- Removal of all data attributes
I hope you enjoyed this article, feel free to message me with any questions, comments, or corrections. All code presented within this series is available in my fork of the official JavaScript30 GitHub repository which is located at:
Up Next
Next in the A New Vue On JavaScript30 series is:
Top comments (1)
Nice! I have started something similar with re writing some JS30 exercie in StimulusJS. Always interesting so see different immplementations