Project Build – Reaction Timer

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
  }
},
Now we need to define the 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)
  }
}
In Block.vue, type vue and select <vue> with default.vue.  This will add to the file empty template, script, and style elements.
In the template, we’ll create a div with text that says “click me” and give it a class name block.
<template>
<div class="block">
click me
</div>
</template>
In the script element, we’ll add a delay prop so that we can pass in the value of the delay when an instance of the Block component is created.
export default { 
  props: ['delay']
}
And we’ll stylize the div.
<style>
  .block {
    width: 400px;
    border-radius: 20px;
    background: #0faf87;
    color: white;
    text-align: center;
    padding: 100px 0;
    margin: 40px auto;
  }
</style>
Next, in the App component, we’ll create an instance of the Block component below the button conditionally based on the value of 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 scoreand 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.

Leave a Reply

Your email address will not be published. Required fields are marked *