絶賛、Javascriptを勉強中で、ちょうど案件で診断コンテンツを作成することになりました。
記事でヒットするのは、JQueryのものなどあったのですが、あえてJavascriptで挑戦してみました!
完成ソース
こちらが、完成したものになります。
今回、診断コンテンツが2種ありそうだったので、設問や結果の文言などは、HTMLに直書きをするようにしました。
もし、診断コンテンツが一つだけになるなら、scriptをまとめてもいいと思います。
See the Pen Untitled by komame (@komame) on CodePen.
ざっくりとした仕組みについて
まず、こちらは画面遷移型の診断コンテンツです。
設問や結果の表示は、HTML側でscriptを記述するような作りにして、設問数や結果の分岐は、連想配列の数に応じるので、使い回しができるようにしました。
選択ボタンには、data属性でそれぞれ数値をもたせ、選択した合計値をもとに、結果が分岐するような作りになっています。
一点、気に入っていない箇所があります。それは、選択した数値の配列を合算を、totalScoreという変数に格納したのですが、こちらの数値をHTML側に記述しているところです!
scirpt側で記述していると、showResultText();で値が渡せなくて、きっと他にやり方があるんだと思うんですが。。
ソースの説明
スタートボタンをクリックしたときの挙動
スタートボタンをクリックしたときの関数をstartQuiz()にまとめています。
currentQuestionIndexは、連想配列を取り出すときに必要で、初期化で0にしておきます。
currentQuestionIndex = 0;
startButton.addEventListener('click', () => {
startQuiz();
})
function startQuiz() {
startButton.classList.add('hide')
resultContainer.classList.add('hide')
questionContainer.classList.remove('hide')
showQuestion()
}
スタートボタンをクリックして、設問を表示させる
スタートボタンをクリックしたときに、showQuestion()という関数を実行させます。
設問は、questionElement(id=”question”)の要素に、連想配列のquestionがcurrentQuestionIndexをキーに挿入されます。
選択肢は、answerButtonsElementというid=”answer-buttons”の配下に、ボタンタグを生成し、連想配列のanswersのtext、valueを挿入しています。
valueは、結果を判定するために、それぞれ数値をもたせて、その得点数で最後分岐するようにしたいので、ボタンタグにdata-valueという属性が入るようにしています。
function showQuestion() {
// 連想配列の値をそれぞれ挿入
// タイトル
questionElement.innerText = questions[currentQuestionIndex].question;
// 設問
questions[currentQuestionIndex].answers.forEach(answer => {
// ボタンタグを生成して、設問を挿入
const button = document.createElement('button')
button.innerText = answer.text;
const value = answer.value;
button.dataset.value = value;
answerButtonsElement.appendChild(button)
// 選択肢をクリックをする
button.addEventListener('click', selectAnswer)
})
}
questionとanswerの連想配列は、HTML側に記述しています。
選択ボタンをクリックしたときの挙動
そして、選択したボタンをクリックすると、selectAnswerという関数が実行されます。
選択したボタンのdata-value属性の数値を取得して、scoreArrayという配列に格納します。
ここで、currentQuestionIndex++と配列の数を加算して、次の問題に切り替わるようにしています。
また、resetState()でanswerButtonsElement.appendChild(button)で、挿入したボタンを全削除しています。
if分岐で、最後の設問をクリックしたら、showResult()という関数に移ります。
// 回答の選択
function selectAnswer(e) {
const selectedButton = e.target;
// 選択したボタンのvalueを取得
const scoreData = parseInt(selectedButton.dataset.value);
// 配列に格納
scoreArray.push(scoreData);
if (currentQuestionIndex < questions.length - 1) {
resetState()
currentQuestionIndex++
// console.log(currentQuestionIndex);
// console.log(scoreArray)
// nextButton.classList.remove('hide');
showQuestion()
} else {
showResult();
}
}
function resetState() {
// https://into-the-program.com/removechild/
// これで子要素をすべて削除
while (answerButtonsElement.firstChild) {
answerButtonsElement.removeChild(answerButtonsElement.firstChild)
}
}
resetState()で、挿入したボタンを削除する
子要素の削除の仕方がよくわからなかったのですが、下記を参考にしました。
結果を表示する
設問のブロックをhideというクラスを付与して、非表示にし、
代わりに、resultContainerの要素のhideクラスを削除して、表示させます。
showResultText()の関数は、HTML側に記述していますが、内容は下記のコメントアウトしている内容になります。
今回、診断コンテンツが2パターンある予定だったので、文言や結果の箇所だけページ固有とさせたかったため、HTML側で記述させるようにしました。
結果の文言は、連想配列から呼び出すようにしていますが、result.textの箇所にそのまま書いてもいいですね。
// 結果の表示
function showResult() {
console.log(scoreArray)
questionContainer.classList.add('hide');
resultContainer.classList.remove('hide');
// 下記のコメントアウトの部分をshowResuktText()として、それぞれのページで記述する
// 配列の数値を合算。
// const totalScore = scoreArray.reduce(function (sum, currentValue) {
// return sum + parseInt(currentValue);
// }, 0);
// for (let i = 0; i < results.length; i++) {
// const result = results[i];
// if (totalScore <= 3 && i === 0) {
// scoreElement.innerText = result.text + totalScore;
// break;
// } else if (totalScore < 6 && i === 1) {
// scoreElement.innerText = result.text + totalScore;
// break;
// } else if (i === 2) {
// scoreElement.innerText = result.text + totalScore;
// break;
// }
// }
// showResultTextのfunctionを、それぞれのhtmlの箇所で記述をする。
showResultText();
}
結果の分岐を変更したい場合は、if (totalScore <= 3 && i === 0) 以降の内容を変更してください。
もう一度診断するボタンをクリックの挙動
もう一度診断するボタンをクリックすると、currentQuestionIndex = 0;で初期化をさせ、scoreArray = [];で配列の中身を空にします。
// もう一度診断するのボタンをクリック
restartButton.addEventListener('click', () => {
resultContainer.classList.add('hide')
startButton.classList.remove('hide')
currentQuestionIndex = 0;
scoreArray = [];
resetState()
})
最後に
途中、配列の格納の仕方やループの回し方などがとても混乱しましたが、なんとか作成できました。
診断コンテンツによっては、次へや戻るボタンがあるものもあるので、それが必要になったら、また複雑になりそうだ。。。