Nuxt3 Reaction Timer Game

Nishant Aanjaney Jalan
4 min readApr 29, 2022

--

If you do not know what Nuxt3 is, then you should definitely check out my story on how to get started.

Preview

Creating the Project

Now assuming that you are aware of Nuxt3 and have installed the required components, we can get started with the initial commands

npx nuxi init reaction-timer-game
cd reaction-timer-game
npm install
code .

You will now see VS Code Editor open with the current project. app.vue is the main file that we will be working with. Note that you shouldn’t change the name of this file as Nuxt3 is very specific with the directory structure and file names. Open the terminal in VS code and run the command:

npm run dev

and open http://localhost:3000 in your favorite browser to view Nuxt’s starter page.

Adding Bootstrap to the project

We need to add bootstrap inside the <head /> tag of the page but there is no index.html file in this project, so how are we going to access it? We have to configure the code inside the nuxt.config.ts file to add it. Open that file and replace the export with the following code to add bootstrap in the project.

export default defineNuxtConfig({
meta: {
link: [{
rel: 'stylesheet',
href: 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css'
}],
script: [{
src: 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js'
}]
}
})

Creating the Box Component

In the root directory, create a folder called components/. Note that the name of the folder should be exact. Inside this folder, create a file called Box.vue. This can named anything but since we are coding a box component, that is what we will name it.

For the HTML content of the Box.vue component, we add a <template /> tag and a <div /> inside it.

<template>
<div></div>
</template>

Now we want the positioning classes to be done from outside the component, so we can add a prop to this component through the <script /> tag. We first create an interface of the props we’d like to accept along with its type and pass that interface as a generic parameter to a function, defineProps(). This function need not to be imported as Nuxt will understand the function internally and auto-import it at the time of bundling the code into the JavaScript file.

<script lang="ts" setup>
interface BoxProps {
className: string
}
const props = defineProps<BoxProps>()
</script>

Inside the HTML part of this page, we have to dynamically assign the class name and we can do that by simply adding a :.

<template>
<div :class="`${className} reaction-box`">
<p>Click here as soon as the box turns green.</p>
</div>
</template>

I wish to style this box and so I shall add some CSS rules inside the <style /> tag. The keyword scoped makes sure that these styles are valid for only that file and tags from other files will not be affected by them.

<style scoped>
.reaction-box {
width: 500px;
height: 400px;
display: flex;
justify-content: center;
align-items: center;
border: 1px dashed green;
color: green;
background-color: #eeeeee;
cursor: pointer;
}
</style>

To add this component to app.vue, you just have to add <Box className="mx-auto" /> inside the <div />. You needn’t import the file since it lives inside of the components/ directory, Nuxt3 will auto-import it.

Adding life to the code

In Box.vue,

We need to make this code fully functional now. At first, we have to add two more props to the Box.vue component,

interface BoxProps {
className: string;
showGreen: boolean;
playing: boolean;
}

and conditionally add a class based on their values:

<div :class="`${className} reaction-box ${showGreen && playing && 'bg-green'}`">
<p v-if="playing">Click here as soon as the box turns green.</p>
<p v-else>Press the start button.</p>
</div>

and the following scoped style,

.bg-green {
background-color: green;
}

On clicking the box, we have to emit an event back to app.vue so that it can calculate the difference in the time. In order to do this, in the <script />, we can work on

const emit = defineEmits(['clicked']);const handleClick = () => {
if (props.showGreen) {
emit('clicked');
}
}

In app.vue,

We are now done with the component; back to app.vue. We add some state values in the script setup and add a handleClick function.

<script lang="ts" setup>
const showGreen = ref(false)
const playing = ref(false)
const diff = ref(0)
function handleClick(){
console.log("box clicked.")
}
</script>
[...]
<Box className="mx-auto" :showGreen="showGreen" :playing="playing" @clicked="handleClick" />

We need a button to start the game, we add this button and write the code for when we click this button:

<script lang="ts" setup>
[...]
function startGame() {
playing.value = true
showGreen.value = false
const reactionTime = (Math.random() * 5000 >> 0) + 2000
setTimeout(() => {
showGreen.value = true
time0 = new Date().getTime()
}, reactionTime)
}
</script>
[...]
<Box [...] />
<div class="mt-4 text-center">
<button class="btn btn-primary btn-lg" :disabled="playing" @click="startGame">
Start
</button>
</div>

Now that we have started the game, we can write the logic for when it ends, i.e. when the user clicks on the box.

function handleClick() {
const time = new Date().getTime()
showGreen.value = false
playing.value = false
diff.value = time - time0
}

We record the diff value which is the reaction time that the user took to click the box. We can output this in a <p />.

<button [...]>
Start
</button>
<p v-if="diff" class="text-center mt-3">Your reaction was {{ diff }} ms.</p>

Voilà! We have made it!

You can view the full source code on my GitHub repo.

--

--

Nishant Aanjaney Jalan

SWE Intern@JetBrains | UG Student | CS Tutor | Android & Web Developer | OCJP 8 | https://cybercoder-naj.github.io