31. Project 1 Preview & Setup
App will contain 3 components.
App.vue Block.vue Results.vue
Create a new project.
$ vue create reaction-timer $ cd reaction-timer
Remove HelloWorld component from components directory and App.vue.
Add heading to template.
<template> <h1>Ninja Reaction Timer</h1> </template>
Create 2 new files in the components directory named Block.vue and Results.vue.
Open the terminal and start the server.
$ npm run serve
32. Starting a New Game
Want to allow user to start new game. When button is pressed, need to know if player is playing game. Based on boolean, we’ll display different info. Let’s add button with a handler named start.
<template> <h1>Ninja Reaction Timer</h1> <button @click="start">play</button> </template>
Next, we need to add some data properties. isPlaying
will specify whether or not the user is playing the game and delay will specify the delay
that will occur between when the user presses the button and the blue box appears.
data() { return { isPlaying: false, delay: null } },
start
method. The method will initialize the delay
property and flip isPlaying
to true
.methods: { start() { this.delay = 2000 + Math.random() * 5000 this.isPlaying = true console.log(this.delay) } }
div
with text that says “click me” and give it a class name block.<template> <div class="block"> click me </div> </template>
export default { props: ['delay'] }
<style> .block { width: 400px; border-radius: 20px; background: #0faf87; color: white; text-align: center; padding: 100px 0; margin: 40px auto; } </style>
isPlaying
and pass to the Block component the value of delay. We also include the disabled
attribute in the button element and bind the isPlaying
property to is so that the button is disabled if they user is playing the game.<template> <h1>Ninja Reaction Timer</h1> <button @click="start" :disabled="isPlaying">play</button> <Block v-if="isPlaying" :delay="delay"></Block> </template>
33. Component Lifecycle Hooks
Each Vue component has a set of life-cycle hooks (methods) that can be defined in the export object. The hooks are:
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
We now want show the block only after the number of milliseconds in delay
has expired. To accomplish this we will create a data property named showBlock
, initialize it to false, and make the block conditional on the value in showBlock. Then, after the block has been mounted (using a hook), we will start a timer. When the timer expires we’ll set showBlock
to true.
data() { return { showBlock: false } }, mounted() { setTimeout(() => { this.showBlock = true }, this.delay) },
After adding the conditional to the div we have the following template.
<template> <div v-if="showBlock" class="block"> click me </div> </template>
34. Creating a Reaction Timer
As soon as the user sees the block, they will click on the block and the program will compute the amount of time between the time the block appeared and the time the use clicked it. We’ll accomplish this by creating two data properties: timer
and reactionTime
; and two methods: startTimer
and stopTimer
.
When the block is first displayed we’ll call startTime
. startTimer
will call setInterval
and store the interval in timer
. setInterval
will add 10 to reactionTime
every 10 milliseconds. We’ll register stopTimer
as the click event handler for the block and inside stopTimer
we’ll call clearInterval
, passing to it timer
, to stop the interval timer.
data() { return { showBlock: false, timer: null, reactionTime: 0 } }, mounted() { setTimeout(() => { this.showBlock = true this.startTimer() }, this.delay) }, methods: { startTimer() { this.timer = setInterval(() => { this.reactionTime += 10; }, 10) }, stopTimer() { clearInterval(this.timer) console.log(this.reactionTime) } }
All we need to do now is register stopTimer for the click event.
<template> <div v-if="showBlock" class="block" @click="stopTimer"> click me </div> </template>
35. Emitting Custom Events with Data
When the user presses the block, we need to somehow pass back the reaction time from the Block component to the App component so that it can later be displayed in the Results component.
We accomplish this, in the Block component, by emitting an end event when the user clicks on the block. When we emit the end event, we’ll pass the reactionTime
to any handler that is listening for the event.
stopTimer() { clearInterval(this.timer) this.$emit('end', this.reactionTime) }
In the App component we’ll first register an event listener named endGame
for the end event on the Block element. In that handler we’ll save the reaction time in a new data property named score
and set isPlaying
to false.
data() { return { ... score: null } }, methods: { ... endGame(reactionTime) { this.score = reactionTime this.isPlaying = false } }
Now to temporarily display the score we’ll add a <p>
element to the template and conditionally display it based on a new data property named showResult
which will be triggered to true and false, when the game ends and starts, respectively.
<template> ... <p v-if="displayResults">Reaction Time: {{ score }}</p> </template>
data() { return { isPlaying: false, delay: null, score: null, displayResults: false } }, methods: { start() { this.delay = 2000 + Math.random() * 5000 this.isPlaying = true this.displayResults = false; }, endGame(reactionTime) { this.score = reactionTime this.isPlaying = false this.displayResults = true } }
36. Challenge – Showing a Results Component
When the game ends, show the results in a Results component.
In Results.vue we create a prop named score that will be set by the App component, and display the score in the template.
<template> <p>Reaction time: {{ score }} milliseconds</p> </template> <script> export default { ... props: ['score'] ... } </script>
In App.js we import the Results component and display it in the template.
<template> ... <Results v-if="displayResults" :score="score"></Results> </template> <script> import Block from './components/Block.vue' import Results from './components/Results.vue' export default { ... components: { Block, Results } ...
37. Finishing Touches
Now we’ll give the user a rank, like “ninja fingers”, based on their score.
In the Results component, we’ll add a data property named rank and set it in the mounted hook as well as display it.
<template> <p>Reaction Time: {{ score }} milliseconds</p> <p>Rank: {{ rank }} </p> </template>
data() { return { rank: null } }, mounted() { if (this.score< 250) { this.rank = 'Ninja fingers' } else if (this.score < 400) { this.rank = 'Rapid reflexes' } else { this.rank = 'Snail pace' } }
© 2022, Eric.