初心者向け。Vue.jsでクイズアプリの作り方を詳しく解説

1819 回閲覧されました
この記事はこれからVue.jsでアプリを作りたい人のための解説です。
気軽に作ることができるようにjsdelivrを使いスタイル管理はBootstrapを使います。
ターミナルでコマンドからVue.jsをインストールした場合methodsやdataなどの書き方が多少変わるので注意してください。(今回の解説はjsdelivr用です)
デモは↓で実際の操作はこのページでできます。
クイズの問題と答えがあって問題に対して答えを選びますが正解、間違いが表示されます。
そして全ての解答が終わったら何問正解かが表示されます。
それでは解説しますがまずは↓をコピペしてください。
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> | |
<title>Vueで作ったクイズ</title> | |
</head> | |
<body> | |
<div id="app" class="row"> | |
//ここにHTMLを書きます | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> | |
<script> | |
var app = new Vue({ | |
el: '#app', | |
//ここにdataやmethodなどを書きます | |
}) | |
</script> | |
<style> | |
body{ | |
padding: 50px; | |
} | |
.end{ | |
background-color:skyblue; | |
text-align: center; | |
color:#fff; | |
font-size: 20px; | |
padding: 20px 0; | |
} | |
</style> | |
</body> | |
</html> |
まずは↓の状態にします。
コードを↓にします。
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> | |
<title>Vueで作ったクイズ</title> | |
</head> | |
<body> | |
<div id="app" class="row"> | |
//ここから追加 | |
<div class="offset-3 col-6"> | |
<div class="card-body"> | |
<p class="badge badge-dard">第1問</p> | |
<br> | |
<h4 class="card-title">高校数学にない分野は?</h4> | |
<hr> | |
<button | |
type="button" | |
class="btn btn-primary btn-lg btn-block text-left" | |
>単振動</button> | |
<button | |
type="button" | |
class="btn btn-primary btn-lg btn-block text-left" | |
>三角関数</button> | |
<button | |
type="button" | |
class="btn btn-primary btn-lg btn-block text-left" | |
>微分</button> | |
</div> | |
</div> | |
//ここまで追加 | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> | |
<script> | |
var app = new Vue({ | |
el: '#app', | |
}) | |
</script> | |
<style> | |
body{ | |
padding: 50px; | |
} | |
.end{ | |
background-color:skyblue; | |
text-align: center; | |
color:#fff; | |
font-size: 20px; | |
padding: 20px 0; | |
} | |
</style> | |
</body> | |
</html> |
第1問と書いてある所が変わることができるようにするのと問題と答えが変化していくのをv-forディレクティブで表現します。
v-forディレクティブはこの記事に解説があります。
コードを↓にします。
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> | |
<title>Vueで作ったクイズ</title> | |
</head> | |
<body> | |
<div id="app" class="row"> | |
<div class="offset-3 col-6"> | |
<div class="card-body"> | |
<p class="badge badge-dard">第{{number +1 }}問</p> //この行を修正 | |
<br> | |
<h4 class="card-title">{{currentQuestion.question}}</h4> //この行を修正 | |
<hr> | |
//ここから修正 | |
<button | |
type="button" | |
class="btn btn-primary btn-lg btn-block text-left" | |
v-for="(answer,index) in currentQuestion.answers" | |
>{{answer}}</button> | |
//ここまで修正 | |
</div> | |
</div> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> | |
<script> | |
var app = new Vue({ | |
el: '#app', | |
data:{ | |
//ここから追加 | |
number:0, | |
questions:[ | |
{ | |
question:'高校数学にない分野は?', | |
answers:['単振動','三角関数','微分'], | |
answer:1 | |
}, | |
{ | |
question:'ジャンプに連載されなかった漫画は?', | |
answers:['ナルト','ジョジョの奇妙な冒険','鋼の錬金術師'], | |
answer:3 | |
}, | |
{ | |
question:'YouTubeでゲーム実況されている青鬼の亜種作品は?', | |
answers:['家具鬼','阿部鬼','魂鬼'], | |
answer:2 | |
}, | |
], | |
//ここまで追加 | |
}, | |
//ここから追加 | |
computed:{ | |
currentQuestion:function(){ | |
return this.questions[this.number]; | |
} | |
}, | |
//ここまで追加 | |
}) | |
</script> | |
<style> | |
body{ | |
padding: 50px; | |
} | |
.end{ | |
background-color:skyblue; | |
text-align: center; | |
color:#fff; | |
font-size: 20px; | |
padding: 20px 0; | |
} | |
</style> | |
</body> | |
</html> |
15行目の「第{{number +1 }}問」ですがnumberを定義しないとエラーになるので42行目で定義しています。
numberを0で定義していて最初の問題が第一問なのでnumber+1にしています。
表示される問題と答えですが変化していきます。(第一問、第二問、第三問と変わっていくということ)
だからcomputedを使います。
computedの解説はこの記事に情報がありますがVue.jsを使い始めた人にとって難しいと思うので理解できない場合はそんなもんかと思ってください、その内理解できます。
問題と答えですが43行目〜59行目の配列を使い配列のインデックス番号は最初は0なので42行目のnumberを使います。(配列のインデックス番号に使うためにnumberを0で定義しました)
25行目の「(answer,index) in currentQuestion.answers」ですがv-forディレクティブの使い方は(表示する内容,表示する内容のインデックス番号) in 全体とします。
indexは必要な場合のみつけて今回は使うのでつけていてv-forディレクティブのインデックス番号は0からです。
表示する内容ですが今回は配列になっていて(46行目、51行目、56行目)46行目だと単振動がindex=0で三角関数がindex=1で微分がindex=2です。
表示する内容の名前はなんでもいいですが全体の名前にanswersを使っているのでanswerにしています。
通常のv-forディレクティブの全体は43行目のquestionsを使うのですが今回はcomputedの中にv-forを使う場合の書き方です。
だから表示する問題は17行目の「{{currentQuestion.question}}」で25行目のv-forディレクティブの全体は「currentQuestion.answers」になります。
これで問題と答えを変えることができる情報はできましたが解答をクリックしてもまだ問題と答えは次の内容になりません。
解答をクリックした時に69行目の「this.number」のnumberを増やすことで問題と解答を変えます。
問題の解答をクリックしてただ次の問題に移っても味気ないので解答が正解だったら正解と表示されるようにして、間違っている場合ははずれと表示されるようにします。
コードを↓にします。
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> | |
<title>Vueで作ったクイズ</title> | |
</head> | |
<body> | |
<div id="app" class="row"> | |
<div class="offset-3 col-6"> | |
<div class="card-body"> | |
<p class="badge badge-dard">第{{number +1 }}問</p> | |
<br> | |
<h4 class="card-title">{{currentQuestion.question}}</h4> | |
<hr> | |
<button | |
type="button" | |
class="btn btn-primary btn-lg btn-block text-left" | |
v-for="(answer,index) in currentQuestion.answers" | |
@click="selectAnswer(index)" //この行を追加 | |
>{{answer}}</button> | |
</div> | |
</div> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> | |
<script> | |
var app = new Vue({ | |
el: '#app', | |
data:{ | |
number:0, | |
questions:[ | |
{ | |
question:'高校数学にない分野は?', | |
answers:['単振動','三角関数','微分'], | |
answer:1 | |
}, | |
{ | |
question:'ジャンプに連載されなかった漫画は?', | |
answers:['ナルト','ジョジョの奇妙な冒険','鋼の錬金術師'], | |
answer:3 | |
}, | |
{ | |
question:'YouTubeでゲーム実況されている青鬼の亜種作品は?', | |
answers:['家具鬼','阿部鬼','魂鬼'], | |
answer:2 | |
}, | |
], | |
}, | |
computed:{ | |
currentQuestion:function(){ | |
return this.questions[this.number]; | |
} | |
}, | |
//ここから追加 | |
methods:{ | |
selectAnswer(index){ | |
if(index + 1 == this.currentQuestion.answer){ | |
alert('正解'); | |
}else{ | |
alert('はずれ'); | |
} | |
this.number ++; | |
} | |
} | |
//ここまで追加 | |
}) | |
</script> | |
<style> | |
body{ | |
padding: 50px; | |
} | |
.end{ | |
background-color:skyblue; | |
text-align: center; | |
color:#fff; | |
font-size: 20px; | |
padding: 20px 0; | |
} | |
</style> | |
</body> | |
</html> |
23行目ですが解答をクリックした時を考えるのでv-onディレクティブを使います。
v-onディレクティブはこの記事に説明があります。
selectAnswerの中身は62行目〜69行にありselectAnswerの引数に22行目のindexを使っています。(ここでindexを使うためにv-forディレクティブにindexも使っています)
39行目と44行目と49行目のanswerの数値ですが69行目の「index + 1 == this.currentQuestion.answer」に使うために上手く数値を考えています。
例えば第一問なら正解は短振動なのですがindex=0なのでindex+1=1だからanswerを1にしています。
こんな感じで第二問、第三問のanswerの値を決めています。
63行目〜64行目が正解の時で66行目がハズレの時です。
あたりはずれのいずれの場合でも解答をクリックしたら次の問題に移るように68行目があります。
これで問題が表示されて解答をクリックしたら次の問題に移りますが三問目の解答のクリックが終わったら何も反応しないので全問中何問正解かを表示します。
コードを↓にします。
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> | |
<title>Vueで作ったクイズ</title> | |
</head> | |
<body> | |
<div id="app" class="row"> | |
<div class="offset-3 col-6"> | |
//ここから追加 | |
<div | |
class="card" | |
v-if="status" | |
> | |
//ここまで追加 | |
<div class="card-body"> | |
<p class="badge badge-dard">第{{number+1}}問</p> | |
<br> | |
<h4 class="card-title">{{currentQuestion.question}}</h4> | |
<hr> | |
<button | |
type="button" | |
class="btn btn-primary btn-lg btn-block text-left" | |
v-for="(answer,index) in currentQuestion.answers" | |
@click="selectAnswer(index)" | |
>{{answer}}</button> | |
</div> | |
</div> //この行を追加 | |
//ここから追加 | |
<div v-else | |
class="end" | |
>終了です。<br>{{questions.length}}問中{{correctCount}}問正解です。</div> | |
//ここまで追加 | |
</div> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> | |
<script> | |
var app = new Vue({ | |
el: '#app', | |
data:{ | |
questions:[ | |
{ | |
question:"高校数学にない分野は?", | |
answers:["単振動","三角関数","微分"], | |
answer:1 | |
}, | |
{ | |
question:"ジャンプに掲載されてなかった漫画は?", | |
answers:["ナルト","ジョジョの奇妙な冒険","鋼の錬金術師"], | |
answer:3 | |
}, | |
{ | |
question:"YouTubeでゲーム実況されている青鬼の亜種作品は?", | |
answers:["家具鬼","阿部鬼","魂鬼"], | |
answer:2 | |
}, | |
], | |
number:0, | |
questionLength:3, //この行を追加 | |
status:true, //この行を追加 | |
correctCount:0 //この行を追加 | |
}, | |
computed:{ | |
currentQuestion:function(){ | |
return this.questions[this.number]; | |
} | |
}, | |
methods:{ | |
selectAnswer(index){ | |
if(index+1 == this.currentQuestion.answer){ | |
this.correctCount++; | |
alert('正解') | |
} | |
this.number++; | |
//ここから追加 | |
this.questionLength--; | |
if(this.questionLength == 0){ | |
this.status = false; | |
} | |
//ここまで追加 | |
} | |
} | |
}) | |
</script> | |
<style> | |
body{ | |
padding: 50px; | |
} | |
.end{ | |
background-color:skyblue; | |
text-align: center; | |
color:#fff; | |
font-size: 20px; | |
padding: 20px 0; | |
} | |
</style> | |
</body> | |
</html> |
問題と解答がある時(第三問目まで解答が終わってない時)と答える解答がない時で表示を切り分けるのでv-if、v-elseを使います。
問題と解答がある時をv-ifで表示して解答がなくなった時(正解数を表示する時)をv-elseで表示します。
v-ifとv-elseの使い方はこの記事で解説しています。
問題が正解の場合の正解数を覚えておくために74行目でcorrectCountを定義して84行目(正解の時)に使っています。
v-ifからv-elseに切り替える流れはこうします。
最初はv-ifを表示するためにstatusをtrueにします。(73行目)
問題の数は全部で3なので72行目でquestionLengthを3と定義します。
そして解答を選ぶたびにquestionLengthを1減らします。(91行目)
そしてquestionLengthが0になったらstatusをfalseにして(92行目〜94行目)v-elseの内容を表示します。
そして正解の数(correctCount)を42行目で使います。
これで完成です。
Vue.jsをこれから勉強したい人の助けになれば幸いです。