Guess the name of the Pokemon using Vue and the PokeAPI!
A Pen by tiffany choong on CodePen.
<div id="app"> | |
<div | |
:class="{'poke-classic': classic}" | |
class="container" | |
> | |
<transition name="animate-section"> | |
<section | |
v-if="!isPlaying && !isDone" | |
class="poke-section" | |
> | |
<h2>What type of trainer are you?</h2> | |
<div class="poke-intro-trainer"> | |
<div class="poke-ball"></div> | |
<img | |
:class="{active: trainerHovered === 'classic'}" | |
class="poke-trainer-img poke-trainer-img-classic" | |
src="https://raw.githubusercontent.com/tiffachoo/pokesprites/master/trainers/red-rb.png" | |
alt="Trainer red" | |
> | |
<img | |
:class="{active: trainerHovered === 'master'}" | |
class="poke-trainer-img poke-trainer-img-master" | |
src="https://raw.githubusercontent.com/tiffachoo/pokesprites/master/trainers/red-sm.png" | |
alt="Trainer red again" | |
> | |
</div> | |
<button | |
class="button spacer" | |
@click="startGame(151)" | |
@mouseover="trainerHover('classic')" | |
@mouseout="trainerHover" | |
> | |
Classic | |
</button> | |
<button | |
class="button" | |
@click="startGame(0)" | |
@mouseover="trainerHover('master')" | |
@mouseout="trainerHover" | |
> | |
Master | |
</button> | |
</section> | |
</transition> | |
<transition name="animate-section"> | |
<section | |
v-if="isPlaying" | |
class="poke-section" | |
> | |
<h1 class="poke-title"> | |
Who's that pokemon? | |
</h1> | |
<div class="poke-question-wrapper"> | |
<span class="poke-question"> | |
<span class="poke-question-number"> | |
{{ question }} | |
</span> | |
<span class="poke-question-amount"> | |
/ {{ questionAmount}} | |
</span> | |
</span> | |
<span class="poke-score"> | |
{{ score }} | |
<small>pts</small> | |
</span> | |
<div | |
class="poke-image" | |
:class="{'poke-image-success': isChecked && selected.name === answer.name, 'poke-image-error': isChecked && selected.name !== answer.name}" | |
> | |
<img | |
:src="image" | |
alt="No cheating" | |
class="poke-image-img" | |
> | |
</div> | |
<transition-group | |
tag="div" | |
name="animate-options" | |
:class="{'poke-options-answers': isChecked}" | |
class="poke-options" | |
> | |
<button | |
v-for="(pokemon, index) in options" | |
:key="pokemon.name" | |
:data-index="index" | |
:class="{'selected': selected.index === index, 'success': isChecked && pokemon.name === answer.name , 'error': isChecked && selected.index === index && selected.name !== answer.name}" | |
class="poke-options-button" | |
@click="selectAnswer(pokemon.name, index)" | |
>{{ pokemon.name | prettifyName }}</button> | |
</transition-group> | |
<footer class="poke-buttons"> | |
<button | |
:disabled="isChecked || Object.keys(selected).length < 1" | |
class="button" | |
@click="checkAnswer" | |
>Submit</button> | |
<button | |
:disabled="!isChecked" | |
class="button" | |
@click="getNextQuestion" | |
>Next</button> | |
</footer> | |
</div> | |
</section> | |
</transition> | |
<transition name="animate-section"> | |
<section | |
v-if="isDone" | |
class="poke-final" | |
> | |
<h2>Final score</h2> | |
<span class="poke-final-score"> | |
<span class="poke-final-score-number">{{ score }}</span> | |
pts | |
</span> | |
<button | |
class="button" | |
@click="restartGame" | |
>Play again</button> | |
</section> | |
</transition> | |
</div> | |
</div> |
console.clear(); | |
const pkmnTotal = 802; | |
const url = `https://pokeapi.co/api/v2/pokemon/?limit=${pkmnTotal}`; | |
const optionAmount = 4; | |
let pokemonData = []; | |
const prettyNames = { | |
'nidoran-f': 'nidoran♀', | |
'nidoran-m': 'nidoran♂', | |
'mr-mime': 'mr. mime', | |
'deoxys-normal': 'deoxys', | |
'wormadam-plant': 'wormadam', | |
'mime-jr': 'mime jr.', | |
'giratina-altered': 'giratina', | |
'shaymin-land': 'shaymin', | |
'basculin-red-striped': 'basculin', | |
'darmanitan-standard': 'darmanitan', | |
'tornadus-incarnate': 'tornadus', | |
'thundurus-incarnate': 'thundurus', | |
'landorus-incarnate': 'landorus', | |
'keldeo-ordinary': 'keldeo', | |
'meloetta-aria': 'meloetta', | |
'meowstic-male': 'meowstic', | |
'aegislash-shield': 'aegislash', | |
'pumpkaboo-average': 'pumpkaboo', | |
'gourgeist-average': 'gourgeist', | |
'oricorio-baile': 'oricorio', | |
'lycanroc-midday': 'lycanroc', | |
'wishiwashi-solo': 'wishiwashi', | |
'type-null': 'type: null', | |
'minior-red-meteor': 'minior', | |
'mimikyu-disguised': 'mimikyu', | |
'tapu-koko': 'tapu koko', | |
'tapu-lele': 'tapu lele', | |
'tapu-bulu': 'tapu bulu', | |
'tapu-fini': 'tapu fini' | |
} | |
const app = new Vue({ | |
el: '#app', | |
filters: { | |
prettifyName(name) { | |
return prettyNames[name] || name; | |
} | |
}, | |
data() { | |
return { | |
pokemon: [], | |
pkmnAmount: null, | |
score: 0, | |
question: 0, | |
questionAmount: 10, | |
answer: {}, | |
selected: {}, | |
options: [], | |
isPlaying: false, | |
isDone: false, | |
isChecked: false, | |
trainerHovered: null | |
} | |
}, | |
computed: { | |
image() { | |
let url = 'https://raw.githubusercontent.com/tiffachoo/pokesprites/master/pokemon/' | |
let imageUrl = `${url}${this.classic ? 'redblue' : 'sunmoon'}/` | |
let number = this.answer.url.match(/\/(\d+)/)[1]; | |
return `${imageUrl}${number}.png` | |
}, | |
classic() { | |
return this.pkmnAmount <= 151 | |
} | |
}, | |
mounted() { | |
let pokeList = localStorage.getItem('pokeList'); | |
if (pokeList) { | |
pokemonData = JSON.parse(pokeList); | |
} else { | |
this.getData() | |
.then(res => { | |
pokemonData = res.results | |
localStorage.setItem('pokeList', JSON.stringify(res.results)); | |
}); | |
} | |
}, | |
methods: { | |
getData() { | |
return fetch(url) | |
.then(res => res.json()) | |
.catch(err => console.log('errrr')); | |
}, | |
startGame(val) { | |
this.question = 0; | |
this.score = 0; | |
this.isPlaying = true; | |
this.pokemon = [...pokemonData]; | |
this.pkmnAmount = val || pkmnTotal; | |
this.getNextQuestion(); | |
}, | |
getNextQuestion() { | |
this.question += 1; | |
this.resetAnswer(); | |
if (this.question <= this.questionAmount) { | |
let removed = ''; | |
for (let i = 1; i <= optionAmount; i++) { | |
removed = this.pokemon.splice(this.getRandomPokemon(i), 1)[0]; | |
if (i === 1) { | |
this.answer = removed; | |
} else { | |
this.options.push(removed); | |
} | |
} | |
let pos = Math.floor(Math.random() * optionAmount); | |
this.options.splice(pos, 0, this.answer); | |
} else { | |
this.isPlaying = false; | |
this.isDone = true; | |
this.resetAnswer(); | |
} | |
}, | |
selectAnswer(ans, index) { | |
if (!this.isChecked) { | |
this.$set(this.selected, 'name', ans); | |
this.$set(this.selected, 'index', index); | |
} | |
}, | |
checkAnswer() { | |
this.isChecked = true; | |
if (this.selected.name === this.answer.name) { | |
this.score += 10; | |
} | |
}, | |
getRandomPokemon(index) { | |
const diff = (this.question - 1) * 4 + index; | |
return Math.floor(Math.random() * (this.pkmnAmount + 1 - diff)); | |
}, | |
resetAnswer() { | |
this.options = []; | |
this.selected = {}; | |
this.answer = {}; | |
this.isChecked = false; | |
}, | |
restartGame() { | |
this.isDone = false; | |
}, | |
trainerHover(val) { | |
this.trainerHovered = val; | |
} | |
} | |
}) |
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script> |
@import 'https://fonts.googleapis.com/css?family=VT323'; | |
$black: #333; | |
$white: #fff; | |
$primary-color: #f65a52; | |
$primary-color-dark: darken( $primary-color, 10% ); | |
$primary-color-tint: lighten( $primary-color, 15% ); | |
$primary-font: "VT323", monospace; | |
$secondary-color: #94acbd; | |
$secondary-color-dark: darken( $secondary-color, 10% ); | |
$options-color: #c5d5ee; | |
$options-color-hover: darken( $options-color, 7% ); | |
$success-color: #7bd55a; | |
$error-color: #ff8b62; | |
$border-radius-main: 1rem; | |
$border-width: 6px; | |
$border-main: solid $border-width $black; | |
$options-height: 48px; | |
* { box-sizing: border-box; } | |
body { | |
background-color: $primary-color; | |
font-family: $primary-font; | |
font-size: 16px; | |
line-height: 1.875em; | |
color: $black; | |
} | |
.container { | |
width: 100%; | |
max-width: 400px; | |
position: relative; | |
margin: 50px auto; | |
} | |
h2 { | |
font-size: 1.25rem; | |
white-space: nowrap; | |
} | |
.spacer { | |
margin-bottom: 0.5rem; | |
} | |
.button { | |
padding: 0.5em 1.5em; | |
border-radius: $border-radius-main; | |
border: solid 1px transparent; | |
font-family: $primary-font; | |
font-size: 1.5rem; | |
background-color: $primary-color-dark; | |
color: $black; | |
cursor: pointer; | |
transition: 0.35s; | |
&:focus { | |
outline: none; | |
border: 1px dotted lighten($primary-color, 8%); | |
} | |
&:not([disabled]) { | |
&:hover { | |
background-color: $black; | |
color: $primary-color; | |
} | |
} | |
} | |
.poke-section { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
position: relative; | |
max-width: 500px; | |
margin: auto; | |
} | |
.poke-intro-trainer { | |
position: relative; | |
margin-bottom: 1rem; | |
height: 200px; | |
width: 200px; | |
.poke-trainer-img { | |
position: absolute; | |
left: 50%; | |
bottom: 0; | |
height: 200px; | |
opacity: 0; | |
transition: 0.4s cubic-bezier(.22,.75,.53,.99); | |
@media (max-width: 479px) { | |
display: none; | |
} | |
&.active { | |
transform: translateX(-50%); | |
opacity: 1; | |
} | |
&-classic { | |
bottom: 5px; | |
height: 180px; | |
image-rendering: pixelated; | |
transform: translateX(-80%); | |
} | |
&-master { | |
transform: translateX(-20%); | |
} | |
} | |
} | |
.poke-ball { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
height: 150px; | |
width: 150px; | |
border-radius: 50%; | |
background-color: $primary-color-dark; | |
transform: translate(-50%, -50%); | |
overflow: hidden; | |
&::before, | |
&::after { | |
content: ''; | |
position: absolute; | |
} | |
&::before { | |
z-index: 2; | |
top: 50%; | |
left: 50%; | |
height: 40px; | |
width: 40px; | |
border-radius: 50%; | |
border: solid $border-width $primary-color; | |
background-color: $primary-color-tint; | |
transform: translate(-50%, -50%); | |
} | |
&::after { | |
z-index: 1; | |
top: 50%; | |
height: 50%; | |
width: 100%; | |
border-top: solid $border-width $primary-color; | |
background-color: $primary-color-tint; | |
} | |
} | |
.poke-title { | |
position: absolute; | |
top: -2rem; | |
} | |
.poke-question { | |
position: absolute; | |
right: calc(100% + 0.5rem); | |
display: flex; | |
flex-direction: column; | |
align-items: flex-end; | |
&-wrapper { | |
position: relative; | |
width: 250px; | |
} | |
&-number { | |
font-size: 8rem; | |
line-height: 0.4; | |
color: $primary-color-tint; | |
} | |
} | |
.poke-score { | |
position: absolute; | |
top: 6rem; | |
right: calc(100% + 0.5rem); | |
padding-top: 1rem; | |
font-size: 1.25rem; | |
white-space: nowrap; | |
color: $black; | |
&::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
right: 0; | |
width: 40px; | |
height: $border-width; | |
background-color: $black; | |
} | |
} | |
.poke-image { | |
position: relative; | |
z-index: 2; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 250px; | |
height: 250px; | |
border-radius: $border-radius-main; | |
border: $border-main; | |
background-color: $white; | |
overflow: hidden; | |
&::before, | |
&::after { | |
content: ''; | |
position: absolute; | |
z-index: -1; | |
border-radius: 50%; | |
} | |
&::before { | |
width: 100px; | |
height: 100px; | |
background-color: $options-color; | |
opacity: 1; | |
transition: 0.65s ease-in-out; | |
} | |
&::after { | |
width: 100px; | |
height: 100px; | |
border: solid ($border-width * 2) $options-color; | |
transform: scale(0); | |
transition: 0.4s ease-in-out; | |
} | |
&-img { | |
width: auto; | |
height: 150px; | |
} | |
&-success, | |
&-error { | |
&::before { | |
transform: scale(4); | |
opacity: 0.5; | |
} | |
&::after { | |
transform: scale(1); | |
} | |
} | |
&-success { | |
&::before { | |
background-color: $success-color; | |
} | |
&::after { | |
border-color: $success-color; | |
} | |
} | |
&-error { | |
&::before { | |
background-color: $error-color; | |
} | |
&::after { | |
border-color: $error-color; | |
width: 10px; | |
border-radius: $border-radius-main; | |
transform: rotate(45deg); | |
} | |
} | |
} | |
.poke-options { | |
position: relative; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
z-index: 3; | |
top: -30px; | |
padding: 0 20px; | |
margin: 0 auto; | |
width: 170px; | |
border-radius: $border-radius-main; | |
background-color: $black; | |
&:not(.poke-options-answers) { | |
.poke-options-button { | |
&:not(.selected) { | |
&:hover { | |
background-color: $options-color-hover; | |
// color: $primary-color-dark; | |
transform: translateY(-$border-width / 2); | |
} | |
&:active { | |
&::before { | |
transform: translate(-50%, -50%) scale(1); | |
} | |
} | |
} | |
} | |
} | |
&.poke-options-answers { | |
.poke-options-button { | |
cursor: default; | |
&:not(.error):not(.success) { | |
color: $secondary-color; | |
} | |
} | |
} | |
&-button { | |
position: relative; | |
width: 100%; | |
padding: 0.5em; | |
min-width: 200px; | |
max-height: $options-height; | |
border: $border-main; | |
border-radius: $border-radius-main; | |
background-color: $options-color; | |
font-family: $primary-font; | |
font-size: 1.125rem; | |
transition: 0.45s; | |
cursor: pointer; | |
overflow: hidden; | |
&:focus { | |
outline: none; | |
} | |
&::before { | |
content: ''; | |
position: absolute; | |
z-index: -1; | |
left: 50%; | |
top: 50%; | |
height: 200px; | |
width: 200px; | |
border-radius: 50%; | |
background-color: $secondary-color; | |
transform: translate(-50%, -50%) scale(0); | |
transition: 0.2s ease-in-out; | |
} | |
&:not(:last-child) { | |
margin-bottom: $border-width / 2; | |
} | |
&.selected { | |
background-color: $secondary-color; | |
} | |
&.error { | |
background-color: $error-color; | |
} | |
&.success { | |
background-color: $success-color; | |
} | |
} | |
} | |
.poke-buttons { | |
text-align: center; | |
@media (min-width: 480px) { | |
position: absolute; | |
top: 20px; | |
left: 100%; | |
.button { | |
padding-left: calc(1em + 10px); | |
border-top-left-radius: 0; | |
border-bottom-left-radius: 0; | |
transform: translateX(-10px); | |
} | |
} | |
.button { | |
padding: 1em; | |
width: 110px; | |
height: 100px; | |
color: $white; | |
&[disabled] { | |
color: $primary-color-tint; | |
opacity: 0.7; | |
cursor: default; | |
} | |
&:not([disabled]) { | |
&:hover { | |
transform: translateX(0); | |
} | |
} | |
&:not(:last-child) { | |
margin-bottom: $border-width; | |
} | |
} | |
} | |
.poke-final { | |
text-align: center; | |
&-score { | |
display: block; | |
position: relative; | |
margin-bottom: 1rem; | |
&::before { | |
content: ''; | |
position: absolute; | |
z-index: -1; | |
top: 50%; | |
left: 50%; | |
height: 100px; | |
width: 100px; | |
border-radius: 50%; | |
border: solid ($border-width * 2) $primary-color-dark; | |
transform: translate(-50%, -50%); | |
opacity: 0.3; | |
animation: grow 2s infinite ease-in-out; | |
} | |
&-number { | |
font-size: 8rem; | |
line-height: 0.4; | |
color: $primary-color-tint; | |
} | |
} | |
} | |
.poke-classic { | |
.poke-image { | |
&-img { | |
image-rendering: pixelated; | |
} | |
} | |
} | |
.animate-section { | |
&-enter-active, | |
&-leave-active { | |
transition: 0.4s ease-in-out; | |
} | |
&-enter, | |
&-leave-to { | |
opacity: 0; | |
} | |
&-enter { | |
.poke-final-score-number { | |
transform: translateY(-30px); | |
} | |
} | |
&-leave-active { | |
transform: translateX(-30%); | |
} | |
&-enter-active { | |
transition-delay: 0.1s; | |
position: absolute; | |
top: 0; | |
left: 50%; | |
transform: translateX(-50%); | |
} | |
} | |
.animate-options { | |
&-enter-active { | |
transition: 0.4s ease-in-out; | |
@for $i from 4 through 8 { | |
&:nth-child(#{$i}) { | |
transition-delay: 0.2s * ($i - 4); | |
} | |
} | |
} | |
&-enter { | |
transform: rotateX(-45deg); | |
transform-origin: top center; | |
opacity: 0; | |
} | |
&-leave-active { | |
position: absolute; | |
z-index: -1; | |
transition: 0.8s ease-in-out; | |
// @for $i from 0 through 3 { | |
// $child: $i + 1; | |
// &:nth-child(#{$child}) { | |
// top: ($options-height + $border-width / 2) * $i; | |
// } | |
// } | |
&[data-index="0"] { | |
top: 0; | |
} | |
&[data-index="1"] { | |
top: ($options-height + $border-width / 2); | |
} | |
&[data-index="2"] { | |
top: ($options-height + $border-width / 2) * 2; | |
} | |
&[data-index="3"] { | |
top: ($options-height + $border-width / 2) * 3; | |
} | |
} | |
&-leave-to { | |
opacity: 0; | |
} | |
} | |
@keyframes grow { | |
0%, 100% { transform: translate(-50%, -50%) scale(1) } | |
50% { transform: translate(-50%, -50%) scale(0.6) } | |
} |
Guess the name of the Pokemon using Vue and the PokeAPI!
A Pen by tiffany choong on CodePen.