Guess the name of the Pokemon using Vue and the PokeAPI!
A Pen by Menglin Chen 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 Menglin Chen on CodePen.