HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#Руководства</a></p>
1 <p><a>#Руководства</a></p>
2 <ul><li>25 июн 2020</li>
2 <ul><li>25 июн 2020</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Разбираемся, как создавать тесты и викторины для сайта на ванильном JavaScript.</p>
4 </ul><p>Разбираемся, как создавать тесты и викторины для сайта на ванильном JavaScript.</p>
5 <p> vlada_maestro / shutterstock</p>
5 <p> vlada_maestro / shutterstock</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
6 <p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
7 <p>Тесты и викторины хороши не только ради проверки знаний, но и как развлекательный контент, который заставляет пользователей дольше оставаться на сайте.</p>
7 <p>Тесты и викторины хороши не только ради проверки знаний, но и как развлекательный контент, который заставляет пользователей дольше оставаться на сайте.</p>
8 <p>Чтобы их создать, можно воспользоваться сторонними сервисами, но разве это когда-нибудь останавливало хоть одного разработчика? С любовью<a>изобретать велосипеды</a>мы создадим собственный код для встраивания тестов на страницы.</p>
8 <p>Чтобы их создать, можно воспользоваться сторонними сервисами, но разве это когда-нибудь останавливало хоть одного разработчика? С любовью<a>изобретать велосипеды</a>мы создадим собственный код для встраивания тестов на страницы.</p>
9 <p>Репозиторий проекта на <a>GitHub</a></p>
9 <p>Репозиторий проекта на <a>GitHub</a></p>
10 <p>Тест мы поместим в файл<em>quiz.html</em>, чтобы его можно было вставлять с помощью<em>iframe</em>в другие страницы. Давайте сверстаем тест:</p>
10 <p>Тест мы поместим в файл<em>quiz.html</em>, чтобы его можно было вставлять с помощью<em>iframe</em>в другие страницы. Давайте сверстаем тест:</p>
11 &lt;div class="wrapper"&gt; &lt;main class="main"&gt; &lt;div class="quiz__head"&gt; &lt;div class="head__content" id="head"&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut ducimus odit accusamus, illum quas magni provident odio praesentium commodi sint, porro harum, minus cupiditate architecto culpa aut ex dolore officia.&lt;/div&gt; &lt;/div&gt; &lt;div class="quiz__body"&gt; &lt;div class="buttons"&gt; &lt;div class="buttons__content" id="buttons"&gt; &lt;button class="button"&gt;Default button&lt;/button&gt;&lt;br&gt; &lt;button class="button button_wrong"&gt;Wrong answer&lt;/button&gt;&lt;br&gt; &lt;button class="button button_correct"&gt;Correct answer&lt;/button&gt;&lt;br&gt; &lt;button class="button button_passive"&gt;Unclicked button&lt;/button&gt;&lt;br&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class="quiz__footer"&gt; &lt;div class="footer__content" id="pages"&gt;0 / 0&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/main&gt; &lt;/div&gt;<p>Теперь добавим стили:</p>
11 &lt;div class="wrapper"&gt; &lt;main class="main"&gt; &lt;div class="quiz__head"&gt; &lt;div class="head__content" id="head"&gt;Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut ducimus odit accusamus, illum quas magni provident odio praesentium commodi sint, porro harum, minus cupiditate architecto culpa aut ex dolore officia.&lt;/div&gt; &lt;/div&gt; &lt;div class="quiz__body"&gt; &lt;div class="buttons"&gt; &lt;div class="buttons__content" id="buttons"&gt; &lt;button class="button"&gt;Default button&lt;/button&gt;&lt;br&gt; &lt;button class="button button_wrong"&gt;Wrong answer&lt;/button&gt;&lt;br&gt; &lt;button class="button button_correct"&gt;Correct answer&lt;/button&gt;&lt;br&gt; &lt;button class="button button_passive"&gt;Unclicked button&lt;/button&gt;&lt;br&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class="quiz__footer"&gt; &lt;div class="footer__content" id="pages"&gt;0 / 0&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/main&gt; &lt;/div&gt;<p>Теперь добавим стили:</p>
12 body, html { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; font-size: 16px; font-family: helvetica, arial; background: #f9f9f9; color: #111; } .wrapper { width: 100%; height: 100%; display: table; } .main { display: table-cell; vertical-align: middle; text-align: center; } .quiz-frame { border: 0; box-shadow: 0 0 10px rgba(0,0,0,0.5); } .quiz__head { font-size: 20pt; margin: 10px; margin-bottom: 50px; } .head__content { padding: 5px; } .quiz__body { margin: 10px; } .quiz__footer { position: absolute; bottom: 0; display: block; width: 100%; } .footer__content { padding: 5px; } .button { border: 0; border-radius: 10px; background: #6477EB; color: #fff; padding: 10px 25px; width: 70%; font-size: 15pt; display: block; margin: 2px auto; cursor: pointer; } .button_wrong { background: #EB6465; } .button_correct { background: #5EB97D; } .button_passive { background: #B3B3B3; }<p>В файл<em>index.html</em>добавим<em>iframe</em>, чтобы подключить тест:</p>
12 body, html { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; font-size: 16px; font-family: helvetica, arial; background: #f9f9f9; color: #111; } .wrapper { width: 100%; height: 100%; display: table; } .main { display: table-cell; vertical-align: middle; text-align: center; } .quiz-frame { border: 0; box-shadow: 0 0 10px rgba(0,0,0,0.5); } .quiz__head { font-size: 20pt; margin: 10px; margin-bottom: 50px; } .head__content { padding: 5px; } .quiz__body { margin: 10px; } .quiz__footer { position: absolute; bottom: 0; display: block; width: 100%; } .footer__content { padding: 5px; } .button { border: 0; border-radius: 10px; background: #6477EB; color: #fff; padding: 10px 25px; width: 70%; font-size: 15pt; display: block; margin: 2px auto; cursor: pointer; } .button_wrong { background: #EB6465; } .button_correct { background: #5EB97D; } .button_passive { background: #B3B3B3; }<p>В файл<em>index.html</em>добавим<em>iframe</em>, чтобы подключить тест:</p>
13 &lt;iframe src="quiz.html" width="480" height="720" class="quiz-frame"&gt;&lt;/iframe&gt;<p>Смотрим, что получилось:</p>
13 &lt;iframe src="quiz.html" width="480" height="720" class="quiz-frame"&gt;&lt;/iframe&gt;<p>Смотрим, что получилось:</p>
14 <a></a><p>Наверху находится сам вопрос, под ним - варианты ответов, а в самом низу - прогресс прохождения теста.</p>
14 <a></a><p>Наверху находится сам вопрос, под ним - варианты ответов, а в самом низу - прогресс прохождения теста.</p>
15 <p>Тест будет работать с помощью следующих классов:</p>
15 <p>Тест будет работать с помощью следующих классов:</p>
16 <ul><li><strong>Quiz</strong> - сам тест. Содержит все данные, отвечает за переход к следующему вопросу и завершение теста.</li>
16 <ul><li><strong>Quiz</strong> - сам тест. Содержит все данные, отвечает за переход к следующему вопросу и завершение теста.</li>
17 <li><strong>Question</strong> - вопрос. Содержит текст вопроса и варианты ответов.</li>
17 <li><strong>Question</strong> - вопрос. Содержит текст вопроса и варианты ответов.</li>
18 <li><strong>Answer</strong> - ответ. Содержит текст ответа и количество очков.</li>
18 <li><strong>Answer</strong> - ответ. Содержит текст ответа и количество очков.</li>
19 <li><strong>Result</strong> - результат. Содержит финальный текст и количество очков, которое необходимо для достижения этого результата.</li>
19 <li><strong>Result</strong> - результат. Содержит финальный текст и количество очков, которое необходимо для достижения этого результата.</li>
20 </ul><p>Вот сами классы:</p>
20 </ul><p>Вот сами классы:</p>
21 //Класс, который представляет сам тест class Quiz { constructor(type, questions, results) { //Тип теста: 1 - классический тест с правильными ответами, 2 - тест без правильных ответов this.type = type; //Массив с вопросами this.questions = questions; //Массив с возможными результатами this.results = results; //Количество набранных очков this.score = 0; //Номер результата из массива this.result = 0; //Номер текущего вопроса this.current = 0; } Click(index) { //Добавляем очки let value = this.questions[this.current].Click(index); this.score += value; let correct = -1; //Если было добавлено хотя бы одно очко, то считаем, что ответ верный if(value &gt;= 1) { correct = index; } else { //Иначе ищем, какой ответ может быть правильным for(let i = 0; i &lt; this.questions[this.current].answers.length; i++) { if(this.questions[this.current].answers[i].value &gt;= 1) { correct = i; break; } } } this.Next(); return correct; } //Переход к следующему вопросу Next() { this.current++; if(this.current &gt;= this.questions.length) { this.End(); } } //Если вопросы кончились, этот метод проверит, какой результат получил пользователь End() { for(let i = 0; i &lt; this.results.length; i++) { if(this.results[i].Check(this.score)) { this.result = i; } } } } //Класс, представляющий вопрос class Question { constructor(text, answers) { this.text = text; this.answers = answers; } Click(index) { return this.answers[index].value; } } //Класс, представляющий ответ class Answer { constructor(text, value) { this.text = text; this.value = value; } } //Класс, представляющий результат class Result { constructor(text, value) { this.text = text; this.value = value; } //Этот метод проверяет, достаточно ли очков набрал пользователь Check(value) { if(this.value &lt;= value) { return true; } else { return false; } } }<p>Когда классы готовы, можно инстанцировать объекты<em>(создавать экземпляры):</em></p>
21 //Класс, который представляет сам тест class Quiz { constructor(type, questions, results) { //Тип теста: 1 - классический тест с правильными ответами, 2 - тест без правильных ответов this.type = type; //Массив с вопросами this.questions = questions; //Массив с возможными результатами this.results = results; //Количество набранных очков this.score = 0; //Номер результата из массива this.result = 0; //Номер текущего вопроса this.current = 0; } Click(index) { //Добавляем очки let value = this.questions[this.current].Click(index); this.score += value; let correct = -1; //Если было добавлено хотя бы одно очко, то считаем, что ответ верный if(value &gt;= 1) { correct = index; } else { //Иначе ищем, какой ответ может быть правильным for(let i = 0; i &lt; this.questions[this.current].answers.length; i++) { if(this.questions[this.current].answers[i].value &gt;= 1) { correct = i; break; } } } this.Next(); return correct; } //Переход к следующему вопросу Next() { this.current++; if(this.current &gt;= this.questions.length) { this.End(); } } //Если вопросы кончились, этот метод проверит, какой результат получил пользователь End() { for(let i = 0; i &lt; this.results.length; i++) { if(this.results[i].Check(this.score)) { this.result = i; } } } } //Класс, представляющий вопрос class Question { constructor(text, answers) { this.text = text; this.answers = answers; } Click(index) { return this.answers[index].value; } } //Класс, представляющий ответ class Answer { constructor(text, value) { this.text = text; this.value = value; } } //Класс, представляющий результат class Result { constructor(text, value) { this.text = text; this.value = value; } //Этот метод проверяет, достаточно ли очков набрал пользователь Check(value) { if(this.value &lt;= value) { return true; } else { return false; } } }<p>Когда классы готовы, можно инстанцировать объекты<em>(создавать экземпляры):</em></p>
22 //Массив с результатами const results = [ new Result("Вам многому нужно научиться", 0), new Result("Вы уже неплохо разбираетесь", 2), new Result("Ваш уровень выше среднего", 4), new Result("Вы в совершенстве знаете тему", 6) ]; //Массив с вопросами const questions = [ new Question("2 + 2 = ", [ new Answer("2", 0), new Answer("3", 0), new Answer("4", 1), new Answer("0", 0) ]) ]; //Сам тест const quiz = new Quiz(1, questions, results);<p>Здесь создан только один вопрос, чтобы не отвлекать повторяющимся кодом. Вы можете добавить их столько, сколько вам необходимо.</p>
22 //Массив с результатами const results = [ new Result("Вам многому нужно научиться", 0), new Result("Вы уже неплохо разбираетесь", 2), new Result("Ваш уровень выше среднего", 4), new Result("Вы в совершенстве знаете тему", 6) ]; //Массив с вопросами const questions = [ new Question("2 + 2 = ", [ new Answer("2", 0), new Answer("3", 0), new Answer("4", 1), new Answer("0", 0) ]) ]; //Сам тест const quiz = new Quiz(1, questions, results);<p>Здесь создан только один вопрос, чтобы не отвлекать повторяющимся кодом. Вы можете добавить их столько, сколько вам необходимо.</p>
23 <p>Остаётся только прописать логику взаимодействия с пользователем:</p>
23 <p>Остаётся только прописать логику взаимодействия с пользователем:</p>
24 Update(); //Обновление теста function Update() { //Проверяем, есть ли ещё вопросы if(quiz.current &lt; quiz.questions.length) { //Если есть, меняем вопрос в заголовке headElem.innerHTML = quiz.questions[quiz.current].text; //Удаляем старые варианты ответов buttonsElem.innerHTML = ""; //Создаём кнопки для новых вариантов ответов for(let i = 0; i &lt; quiz.questions[quiz.current].answers.length; i++) { let btn = document.createElement("button"); btn.className = "button"; btn.innerHTML = quiz.questions[quiz.current].answers[i].text; btn.setAttribute("index", i); buttonsElem.appendChild(btn); } //Выводим номер текущего вопроса pagesElem.innerHTML = (quiz.current + 1) + " / " + quiz.questions.length; //Вызываем функцию, которая прикрепит события к новым кнопкам Init(); } else { //Если это конец, то выводим результат buttonsElem.innerHTML = ""; headElem.innerHTML = quiz.results[quiz.result].text; pagesElem.innerHTML = "Очки: " + quiz.score; } } function Init() { //Находим все кнопки let btns = document.getElementsByClassName("button"); for(let i = 0; i &lt; btns.length; i++) { //Прикрепляем событие для каждой отдельной кнопки //При нажатии на кнопку будет вызываться функция Click() btns[i].addEventListener("click", function (e) { Click(e.target.getAttribute("index")); }); } } function Click(index) { //Получаем номер правильного ответа let correct = quiz.Click(index); //Находим все кнопки let btns = document.getElementsByClassName("button"); //Делаем кнопки серыми for(let i = 0; i &lt; btns.length; i++) { btns[i].className = "button button_passive"; } //Если это тест с правильными ответами, то мы подсвечиваем правильный ответ зелёным, а неправильный - красным if(quiz.type == 1) { if(correct &gt;= 0) { btns[correct].className = "button button_correct"; } if(index != correct) { btns[index].className = "button button_wrong"; } } else { //Иначе просто подсвечиваем зелёным ответ пользователя btns[index].className = "button button_correct"; } //Ждём секунду и обновляем тест setTimeout(Update, 1000); }<p>Смотрим, что получилось:</p>
24 Update(); //Обновление теста function Update() { //Проверяем, есть ли ещё вопросы if(quiz.current &lt; quiz.questions.length) { //Если есть, меняем вопрос в заголовке headElem.innerHTML = quiz.questions[quiz.current].text; //Удаляем старые варианты ответов buttonsElem.innerHTML = ""; //Создаём кнопки для новых вариантов ответов for(let i = 0; i &lt; quiz.questions[quiz.current].answers.length; i++) { let btn = document.createElement("button"); btn.className = "button"; btn.innerHTML = quiz.questions[quiz.current].answers[i].text; btn.setAttribute("index", i); buttonsElem.appendChild(btn); } //Выводим номер текущего вопроса pagesElem.innerHTML = (quiz.current + 1) + " / " + quiz.questions.length; //Вызываем функцию, которая прикрепит события к новым кнопкам Init(); } else { //Если это конец, то выводим результат buttonsElem.innerHTML = ""; headElem.innerHTML = quiz.results[quiz.result].text; pagesElem.innerHTML = "Очки: " + quiz.score; } } function Init() { //Находим все кнопки let btns = document.getElementsByClassName("button"); for(let i = 0; i &lt; btns.length; i++) { //Прикрепляем событие для каждой отдельной кнопки //При нажатии на кнопку будет вызываться функция Click() btns[i].addEventListener("click", function (e) { Click(e.target.getAttribute("index")); }); } } function Click(index) { //Получаем номер правильного ответа let correct = quiz.Click(index); //Находим все кнопки let btns = document.getElementsByClassName("button"); //Делаем кнопки серыми for(let i = 0; i &lt; btns.length; i++) { btns[i].className = "button button_passive"; } //Если это тест с правильными ответами, то мы подсвечиваем правильный ответ зелёным, а неправильный - красным if(quiz.type == 1) { if(correct &gt;= 0) { btns[correct].className = "button button_correct"; } if(index != correct) { btns[index].className = "button button_wrong"; } } else { //Иначе просто подсвечиваем зелёным ответ пользователя btns[index].className = "button button_correct"; } //Ждём секунду и обновляем тест setTimeout(Update, 1000); }<p>Смотрим, что получилось:</p>
25 <p>Когда пользователь завершит тест, то увидит свой результат:</p>
25 <p>Когда пользователь завершит тест, то увидит свой результат:</p>
26 <a></a><p>Как вы могли заметить, это очень простой тест. Он пригодится, чтобы пользователи могли проверить, насколько хорошо они усвоили материал. Ну или просто для веселья - вот несколько тем для развлекательных тестов:</p>
26 <a></a><p>Как вы могли заметить, это очень простой тест. Он пригодится, чтобы пользователи могли проверить, насколько хорошо они усвоили материал. Ну или просто для веселья - вот несколько тем для развлекательных тестов:</p>
27 <ul><li>Кто ты из "Чародеек" (W. I. T. C. H.).</li>
27 <ul><li>Кто ты из "Чародеек" (W. I. T. C. H.).</li>
28 <li>За кого из "Сверхъестественного" ты выйдешь замуж.</li>
28 <li>За кого из "Сверхъестественного" ты выйдешь замуж.</li>
29 <li>Твой гороскоп на сегодня.</li>
29 <li>Твой гороскоп на сегодня.</li>
30 <li>На какой факультет ты бы попал в Хогвартсе.</li>
30 <li>На какой факультет ты бы попал в Хогвартсе.</li>
31 </ul><p>Всё это может быть очень забавным и вовлекающим, если учитывать особенности своей аудитории. Например, для программистов есть интересные тесты на сайте<a>tproger.ru</a>.</p>
31 </ul><p>Всё это может быть очень забавным и вовлекающим, если учитывать особенности своей аудитории. Например, для программистов есть интересные тесты на сайте<a>tproger.ru</a>.</p>
32 <a></a><em>Такие вопросы развлекают и помогают узнать что-то новое</em><p>Другое дело, если у вас образовательная платформа и результаты теста влияют на итоговую оценку. В этом случае данные о правильных ответах нужно хранить на сервере. Иначе их можно подсмотреть через консоль разработчика:</p>
32 <a></a><em>Такие вопросы развлекают и помогают узнать что-то новое</em><p>Другое дело, если у вас образовательная платформа и результаты теста влияют на итоговую оценку. В этом случае данные о правильных ответах нужно хранить на сервере. Иначе их можно подсмотреть через консоль разработчика:</p>
33 <a></a><p>То же самое касается и таймеров: если вы даёте ограниченное время на прохождение теста, то время начала отсчёта должно храниться на сервере, а не в JS-коде.</p>
33 <a></a><p>То же самое касается и таймеров: если вы даёте ограниченное время на прохождение теста, то время начала отсчёта должно храниться на сервере, а не в JS-коде.</p>
34 <p>С помощью кода из статьи можно создавать сколько угодно тестов. Разве что для каждого придётся дублировать файл<em>app.js</em>, чтобы указать новые вопросы.</p>
34 <p>С помощью кода из статьи можно создавать сколько угодно тестов. Разве что для каждого придётся дублировать файл<em>app.js</em>, чтобы указать новые вопросы.</p>
35 <p>Исправить это можно с помощью HTTP-запросов - вопросы будут храниться на сервере и отправляться пользователю в виде JSON. Это очень распространённая практика в веб-разработке, которую нужно знать каждому разработчику.</p>
35 <p>Исправить это можно с помощью HTTP-запросов - вопросы будут храниться на сервере и отправляться пользователю в виде JSON. Это очень распространённая практика в веб-разработке, которую нужно знать каждому разработчику.</p>
36 <a>Научитесь: Профессия Фронтенд-разработчик + ИИ Узнать больше</a>
36 <a>Научитесь: Профессия Фронтенд-разработчик + ИИ Узнать больше</a>