«Как мне составить тест на JavaScript?» — один из самых распространенных вопросов, задаваемых людьми, изучающими веб-разработку, и на то есть веские причины. Викторины это весело! Это отличный способ узнать о новых предметах, и они позволяют вам привлечь аудиторию чем-то веселым и игривым.
Эта популярная статья была обновлена в 2020 году, чтобы отразить современные лучшие практики JavaScript.
Кодирование вашей собственной викторины JavaScript также является фантастическим упражнением в обучении. Он учит вас, как обращаться с событиями, управлять DOM, обрабатывать ввод данных пользователем, давать обратную связь пользователю и отслеживать его оценки (например, используя хранилище на стороне клиента). И когда у вас есть базовый тест и работает, есть целый ряд возможностей для добавления более продвинутых функций, таких как нумерация страниц. Я вхожу в это в конце статьи .
В этом руководстве я покажу вам, как создать многошаговую викторину по JavaScript, которую вы сможете адаптировать к своим потребностям и добавить на свой сайт. Если вы хотите увидеть, чем мы закончим, вы можете пропустить и посмотреть рабочий тест .
Что нужно знать перед началом
Несколько вещей, которые нужно знать перед началом:
- Это интерфейсное руководство, означающее, что ответы может найти любой, кто знает, как просмотреть исходный код страницы. Для серьезных тестов, данные должны быть обработаны через бэкэнд, который выходит за рамки этого руководства.
- Код в этой статье использует современный синтаксис JavaScript (ES6 +), что означает, что он не будет совместим с любыми версиями Internet Explorer. Тем не менее, он будет отлично работать в современных браузерах, включая Microsoft Edge.
- Если вам нужна поддержка старых браузеров, я написал учебник по JavaScript , совместимый с IE8. Или, если вы хотите освежить в ES6, ознакомьтесь с этим курсом Дарина Хейнера в SitePoint Premium.
- Вам понадобится некоторое знакомство с HTML, CSS и JavaScript, но каждая строка кода будет объяснена индивидуально.
Основная структура вашего JavaScript викторины
В идеале мы хотим, чтобы вопросы и ответы теста были в нашем коде JavaScript, и чтобы наш скрипт автоматически генерировал тест. Таким образом, нам не нужно писать много повторяющихся разметок, и мы можем легко добавлять и удалять вопросы.
Чтобы настроить структуру нашей викторины JavaScript, нам нужно начать со следующего HTML:
-
<div>
для проведения теста -
<button>
для отправки теста -
<div>
для отображения результатов
Вот как это будет выглядеть:
<div id="quiz"></div> <button id="submit">Submit Quiz</button> <div id="results"></div>
Затем мы можем выбрать эти элементы HTML и сохранить ссылки на них в переменных следующим образом:
const quizContainer = document.getElementById('quiz'); const resultsContainer = document.getElementById('results'); const submitButton = document.getElementById('submit');
Далее нам понадобится способ построить тест, показать результаты и собрать все вместе. Мы можем начать с выкладывания наших функций и заполнять их по ходу дела:
function buildQuiz(){} function showResults(){} // display quiz right away buildQuiz(); // on submit, show results submitButton.addEventListener('click', showResults);
Здесь у нас есть функции, чтобы построить тест и показать результаты. Мы немедленно запустим нашу функцию buildQuiz
и showResults
нашу функцию showResults
когда пользователь нажмет кнопку отправки.
Отображение вопросов викторины
Следующее, что нужно нашему тесту, — это несколько вопросов для отображения. Мы будем использовать объектные литералы для представления отдельных вопросов и массив для хранения всех вопросов, составляющих наш тест. Использование массива позволит легко перебирать вопросы:
const myQuestions = [ { question: "Who invented JavaScript?", answers: { a: "Douglas Crockford", b: "Sheryl Sandberg", c: "Brendan Eich" }, correctAnswer: "c" }, { question: "Which one of these is a JavaScript package manager?", answers: { a: "Node.js", b: "TypeScript", c: "npm" }, correctAnswer: "c" }, { question: "Which tool can you use to ensure code quality?", answers: { a: "Angular", b: "jQuery", c: "RequireJS", d: "ESLint" }, correctAnswer: "d" } ];
Не стесняйтесь задавать столько вопросов или ответов, сколько хотите.
Примечание: поскольку это массив, вопросы будут отображаться в порядке их перечисления. Если вы хотите каким-либо образом отсортировать вопросы перед тем, как представить их пользователю, ознакомьтесь с нашим кратким советом по сортировке массива объектов в JavaScript .
Теперь, когда у нас есть список вопросов, мы можем показать их на странице. Мы рассмотрим следующий JavaScript-код построчно, чтобы увидеть, как он работает:
function buildQuiz(){ // variable to store the HTML output const output = []; // for each question... myQuestions.forEach( (currentQuestion, questionNumber) => { // variable to store the list of possible answers const answers = []; // and for each available answer... for(letter in currentQuestion.answers){ // ...add an HTML radio button answers.push( `<label> <input type="radio" name="question${questionNumber}" value="${letter}"> ${letter} : ${currentQuestion.answers[letter]} </label>` ); } // add this question and its answers to the output output.push( `<div class="question"> ${currentQuestion.question} </div> <div class="answers"> ${answers.join('')} </div>` ); } ); // finally combine our output list into one string of HTML and put it on the page quizContainer.innerHTML = output.join(''); }
Сначала мы создаем output
переменную, которая будет содержать весь вывод HTML, включая вопросы и варианты ответов.
Далее мы можем начать строить HTML для каждого вопроса. Нам нужно будет просмотреть каждый вопрос следующим образом:
myQuestions.forEach( (currentQuestion, questionNumber) => { // the code we want to run for each question goes here });
Для краткости мы используем функцию стрелки для выполнения наших операций по каждому вопросу. Поскольку это в цикле forEach , мы получаем текущее значение, индекс (номер позиции текущего элемента в массиве) и сам массив в качестве параметров. Нам нужны только текущее значение и индекс, которые мы будем называть currentQuestion
и questionNumber
соответственно.
Теперь давайте посмотрим код внутри нашего цикла:
// we'll want to store the list of answer choices const answers = []; // and for each available answer... for(letter in currentQuestion.answers){ // ...add an html radio button answers.push( `<label> <input type="radio" name="question${questionNumber}" value="${letter}"> ${letter} : ${currentQuestion.answers[letter]} </label>` ); } // add this question and its answers to the output output.push( `<div class="question"> ${currentQuestion.question} </div> <div class="answers"> ${answers.join('')} </div>` );
Для каждого вопроса мы хотим сгенерировать правильный HTML, и поэтому наш первый шаг — создать массив для хранения списка возможных ответов.
Далее мы будем использовать цикл, чтобы заполнить возможные ответы на текущий вопрос. Для каждого варианта мы создаем переключатель HTML, который заключаем в элемент <label>
. Это сделано для того, чтобы пользователи могли щелкнуть в любом месте текста ответа, чтобы выбрать этот ответ. Если метка была опущена, пользователям пришлось бы нажимать на сам переключатель, который не очень доступен.
Обратите внимание, что мы используем литералы шаблонов , которые являются строками, но более мощными. Мы будем использовать следующие функции:
- многострочные возможности
- больше не нужно экранировать кавычки внутри кавычек, потому что литералы шаблона вместо них используют обратные кавычки
- интерполяция строк, так что вы можете встраивать выражения JavaScript прямо в ваши строки, например:
${code_goes_here}
.
Получив наш список кнопок ответа, мы можем вставить HTML-код вопроса и HTML-ответ в наш общий список результатов.
Обратите внимание, что мы используем шаблонный литерал и некоторые встроенные выражения, чтобы сначала создать вопрос div, а затем создать ответ div. Выражение join
берет наш список ответов и объединяет их в одну строку, которую мы можем вывести в наши answers
div.
Теперь, когда мы сгенерировали HTML для каждого вопроса, мы можем объединить его все вместе и показать на странице:
quizContainer.innerHTML = output.join('');
Теперь наша функция buildQuiz
завершена.
На этом этапе вы сможете запустить тест и увидеть отображаемые вопросы. Обратите внимание, однако, что структура вашего кода важна. Из-за того, что называется временной мертвой зоной , вы не можете ссылаться на массив вопросов, пока он не был определен.
Напомним, что это правильная структура:
// Functions function buildQuiz(){ ... } function showResults(){ ... } // Variables const quizContainer = document.getElementById('quiz'); const resultsContainer = document.getElementById('results'); const submitButton = document.getElementById('submit'); const myQuestions = [ ... ]; // Kick things off buildQuiz(); // Event listeners submitButton.addEventListener('click', showResults);
Отображение результатов викторины
На этом этапе мы хотим создать нашу функцию showResults
чтобы циклически showResults
ответы, проверять их и показывать результаты.
Вот функция, которую мы подробно рассмотрим далее:
function showResults(){ // gather answer containers from our quiz const answerContainers = quizContainer.querySelectorAll('.answers'); // keep track of user's answers let numCorrect = 0; // for each question... myQuestions.forEach( (currentQuestion, questionNumber) => { // find selected answer const answerContainer = answerContainers[questionNumber]; const selector = `input[name=question${questionNumber}]:checked`; const userAnswer = (answerContainer.querySelector(selector) || {}).value; // if answer is correct if(userAnswer === currentQuestion.correctAnswer){ // add to the number of correct answers numCorrect++; // color the answers green answerContainers[questionNumber].style.color = 'lightgreen'; } // if answer is wrong or blank else{ // color the answers red answerContainers[questionNumber].style.color = 'red'; } }); // show number of correct answers out of total resultsContainer.innerHTML = `${numCorrect} out of ${myQuestions.length}`; }
Сначала мы выбираем все контейнеры с ответами в HTML нашего теста. Затем мы создадим переменные, чтобы отслеживать текущий ответ пользователя и общее количество правильных ответов.
// gather answer containers from our quiz const answerContainers = quizContainer.querySelectorAll('.answers'); // keep track of user's answers let numCorrect = 0;
Теперь мы можем просмотреть каждый вопрос и проверить ответы.
// for each question... myQuestions.forEach( (currentQuestion, questionNumber) => { // find selected answer const answerContainer = answerContainers[questionNumber]; const selector = `input[name=question${questionNumber}]:checked`; const userAnswer = (answerContainer.querySelector(selector) || {}).value; // if answer is correct if(userAnswer === currentQuestion.correctAnswer){ // add to the number of correct answers numCorrect++; // color the answers green answerContainers[questionNumber].style.color = 'lightgreen'; } // if answer is wrong or blank else{ // color the answers red answerContainers[questionNumber].style.color = 'red'; } });
Общая суть этого кода:
- найти выбранный ответ в HTML
- справиться с тем, что произойдет, если ответ правильный
- справиться с тем, что происходит, если ответ неверный
Давайте более подробно рассмотрим, как мы находим выбранный ответ в нашем HTML:
// find selected answer const answerContainer = answerContainers[questionNumber]; const selector = `input[name=question${questionNumber}]:checked`; const userAnswer = (answerContainer.querySelector(selector) || {}).value;
Во-первых, мы позаботились о том, чтобы заглянуть в контейнер ответов на текущий вопрос.
В следующей строке мы определяем CSS-селектор, который позволит нам определить, какой переключатель выбран.
Затем мы используем JavaScript querySelector
для поиска нашего селектора CSS в ранее определенном answerContainer
. По сути, это означает, что мы найдем переключатель с ответом.
Наконец, мы можем получить значение этого ответа, используя .value
.
Работа с неполным пользовательским вводом
Но что, если пользователь оставил ответ пустым? В этом случае использование .value
приведет к ошибке, потому что вы не можете получить значение того, чего там нет. Чтобы решить эту проблему, мы добавили ||
, что означает «или», и {}
, который является пустым объектом. Теперь общее утверждение гласит:
- Получить ссылку на наш выбранный элемент ответа ИЛИ, если он не существует, использовать пустой объект.
- Получите значение того, что было в первом утверждении.
В результате значение будет либо ответом пользователя, либо undefined
, что означает, что пользователь может пропустить вопрос, не прерывая нашу викторину.
Оценка ответов и отображение результата
Следующие операторы в нашем цикле проверки ответов позволят нам обрабатывать правильные и неправильные ответы.
// if answer is correct if(userAnswer === currentQuestion.correctAnswer){ // add to the number of correct answers numCorrect++; // color the answers green answerContainers[questionNumber].style.color = 'lightgreen'; } // if answer is wrong or blank else{ // color the answers red answerContainers[questionNumber].style.color = 'red'; }
Если ответ пользователя соответствует правильному выбору, увеличьте количество правильных ответов на один и (необязательно) закрасьте набор вариантов зеленым. Если ответ неправильный или пустой, закрасьте варианты ответа красным (опять же, необязательно).
Когда цикл проверки ответов завершен, мы можем показать, сколько вопросов получил пользователь:
// show number of correct answers out of total resultsContainer.innerHTML = `${numCorrect} out of ${myQuestions.length}`;
И теперь у нас есть рабочая викторина JavaScript!
При желании вы можете обернуть весь тест в IIFE (немедленно вызванное выражение функции) , которое запускается сразу после его определения. Это будет держать ваши переменные вне глобальной области и гарантировать, что ваш тест не мешает другим сценариям, запущенным на странице.
(function(){ // put the rest of your code here })();
Теперь все готово! Не стесняйтесь добавлять или удалять вопросы и ответы и стилизовать тест, как вам нравится.
На этом этапе ваш тест может выглядеть следующим образом (с небольшим количеством стиля):
Добавление пагинации
Теперь у нас есть базовый тест, давайте посмотрим на некоторые более сложные функции. Например, допустим, вы хотите показывать только один вопрос за раз.
Тебе понадобиться:
- способ показать и скрыть вопросы
- кнопки для навигации по викторине.
Нам нужно будет сделать некоторые обновления, поэтому давайте начнем с HTML:
<div class="quiz-container"> <div id="quiz"></div> </div> <button id="previous">Previous Question</button> <button id="next">Next Question</button> <button id="submit">Submit Quiz</button> <div id="results"></div>
Большая часть этой разметки такая же, как и раньше, но теперь мы добавили кнопки навигации и контейнер с викторинами. Контейнер для викторин поможет нам расположить вопросы как слои, которые мы можем показать и скрыть.
Затем, внутри функции buildQuiz
, нам нужно добавить элемент <div>
с классом slide
для хранения контейнеров вопросов и ответов, которые мы только что создали:
output.push( `<div class="slide"> <div class="question"> ${currentQuestion.question} </div> <div class="answers"> ${answers.join("")} </div> </div>` );
Далее, мы можем использовать CSS-позиционирование, чтобы слайды располагались как слои друг над другом. В этом примере вы заметите, что мы используем z-index и непрозрачные переходы, чтобы позволить нашим слайдам постепенно увеличиваться и уменьшаться. Вот как может выглядеть этот CSS:
.slide{ position: absolute; left: 0px; top: 0px; width: 100%; z-index: 1; opacity: 0; transition: opacity 0.5s; } .active-slide{ opacity: 1; z-index: 2; } .quiz-container{ position: relative; height: 200px; margin-top: 40px; }
Теперь добавим JavaScript, чтобы сделать нумерацию страниц. Как и прежде, порядок важен, так что это пересмотренная структура нашего кода:
// Functions // New functions go here // Variables // Same code as before // Kick things off buildQuiz(); // Pagination // New code here // Show the first slide showSlide(currentSlide); // Event listeners // New event listeners here
Мы можем начать с некоторых переменных, чтобы хранить ссылки на наши кнопки навигации и отслеживать, на каком слайде мы находимся. Добавьте их после вызова buildQuiz()
, как показано выше:
// Pagination const previousButton = document.getElementById("previous"); const nextButton = document.getElementById("next"); const slides = document.querySelectorAll(".slide"); let currentSlide = 0;
Далее мы напишем функцию для показа слайда. Добавьте это под существующими функциями ( buildQuiz
и showResults
):
function showSlide(n) { slides[currentSlide].classList.remove('active-slide'); slides[n].classList.add('active-slide'); currentSlide = n; if(currentSlide === 0){ previousButton.style.display = 'none'; } else{ previousButton.style.display = 'inline-block'; } if(currentSlide === slides.length-1){ nextButton.style.display = 'none'; submitButton.style.display = 'inline-block'; } else{ nextButton.style.display = 'inline-block'; submitButton.style.display = 'none'; } }
Вот что делают первые три строки:
- Скройте текущий слайд, удалив класс
active-slide
. - Покажите новый слайд, добавив класс
active-slide
. - Обновите текущий номер слайда.
Следующие строки вводят следующую логику:
- Если мы на первом слайде, скрыть кнопку « Предыдущий слайд» . В противном случае покажите кнопку.
- Если мы на последнем слайде, скрыть кнопку « Следующий слайд» и показать кнопку « Отправить» . В противном случае покажите кнопку « Следующий слайд» и скройте кнопку « Отправить» .
После того, как мы написали нашу функцию, мы можем немедленно вызвать showSlide(0)
чтобы показать первый слайд. Это должно прийти после кода нумерации страниц:
// Pagination ... showSlide(currentSlide);
Далее мы можем написать функции для работы кнопок навигации. Они идут под функцией showSlide
:
function showNextSlide() { showSlide(currentSlide + 1); } function showPreviousSlide() { showSlide(currentSlide - 1); }
Здесь мы используем нашу функцию showSlide
чтобы наши кнопки навигации могли показывать предыдущий и следующий слайды.
Наконец, нам нужно подключить навигационные кнопки к этим функциям. Это идет в конце кода:
// Event listeners ... previousButton.addEventListener("click", showPreviousSlide); nextButton.addEventListener("click", showNextSlide);
Теперь у вашего теста есть рабочая навигация!
Что дальше?
Теперь, когда у вас есть базовая викторина по JavaScript, пришло время проявить творческий подход и поэкспериментировать.
Вот несколько предложений, которые вы можете попробовать:
- Попробуйте разные способы ответа на правильный или неправильный ответ.
- Стиль викторины красиво.
- Добавьте индикатор выполнения.
- Пусть пользователи просматривают ответы перед отправкой.
- Дайте пользователям резюме своих ответов после того, как они отправят.
- Обновите навигацию, чтобы пользователи могли перейти к любому номеру вопроса.
- Создание пользовательских сообщений для каждого уровня результатов. Например, если кто-то набрал 8/10 или выше, назовите его викторина ниндзя.
- Добавьте кнопку, чтобы поделиться результатами в социальных сетях.
- Сохраните свои лучшие результаты, используя localStorage .
- Добавьте таймер обратного отсчета, чтобы увидеть, смогут ли люди побить часы.
- Примените концепции из этой статьи для других целей, таких как оценка цены проекта или социальная викторина «какой персонаж ты».