Creating an Interactive Quiz App Using HTML, CSS, and JavaScript
Introduction
Creating a dynamic and engaging quiz application is an excellent project to enhance your skills in HTML, CSS, and JavaScript. In this tutorial, we will build a quiz app called BrainBurst Quiz that allows users to customize their quiz experience by selecting the number of questions, category, difficulty, and time per question. This project will demonstrate how to fetch data from an external API, create a user-friendly interface, and implement interactive elements to keep users engaged.
Body
- Setting Up the Project
Before diving into the code, let’s organize our project structure. Create a directory for your project and inside it, create three files: index.html
, style.css
, and script.js
.
Project Structure:
brainburst-quiz/
│
├── index.html
├── style.css
└── script.js
2. HTML Structure
The HTML file defines the structure of our quiz application. It includes a start screen with options for quiz customization, the quiz screen where questions are displayed, and the end screen showing the final score.
index.html
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8" />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0" />
<title>BrainBurst Quiz App</title>
<link
rel=”stylesheet”
href=”https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"/>
<link rel=”stylesheet” href=”style.css” />
</head>
<body>
<div class=”container”>
<div class=”start-screen”>
<h1 class=”heading”>BrainBurst Quiz</h1>
<div class=”settings”>
<div class=”setting”>
<label for=”num-questions”>Number of Questions:</label>
<select id=”num-questions”>
<option value=”5">Enter Number</option>
<option value=”5">5</option>
<option value=”10">10</option>
<option value=”15">15</option>
<option value=”20">20</option>
<option value=”30">30</option>
<option value=”40">40</option>
<option value=”50">50</option>
</select>
</div>
<div class=”setting”>
<label for=”category”>Select Category:</label>
<select id=”category”>
<option value=””>Any Category</option>
<option value=”9">General Knowledge</option>
<option value=”10">Books</option>
<option value=”11">Films</option>
<option value=”12">Music</option>
<option value=”14">Television</option>
<option value=”15">Video Games</option>
<option value=”16">Board Games</option>
<option value=”17">Science & Nature</option>
<option value=”18">Computers</option>
<option value=”19">Mathematics</option>
<option value=”20">Mythology</option>
<option value=”21">Sports</option>
<option value=”22">Geography</option>
<option value=”23">History</option>
<option value=”24">Politics</option>
<option value=”25">Art</option>
<option value=”28">Vehicles</option>
</select>
</div>
<div class=”setting”>
<label for=”difficulty”>Select Difficulty:</label>
<select id=”difficulty”>
<option value=””>Any Difficulty</option>
<option value=”easy”>Easy</option>
<option value=”medium”>Medium</option>
<option value=”hard”>Hard</option>
</select>
</div>
<div class=”setting”>
<label for=”time”>Time per Question:</label>
<select id=”time”>
<option value=”10">Enter Time</option>
<option value=”10">10 seconds</option>
<option value=”15">15 seconds</option>
<option value=”20">20 seconds</option>
<option value=”25">25 seconds</option>
<option value=”30">30 seconds</option>
<option value=”60">60 seconds</option>
</select>
</div>
</div>
<button class=”btn start”>Start Quiz</button>
</div>
<div class=”quiz hide”>
<div class=”timer”>
<div class=”progress”>
<div class=”progress-bar”></div>
<span class=”progress-text”></span>
</div>
</div>
<div class=”question-wrapper”>
<div class=”number”>
Question <span class=”current”>1</span>
<span class=”total”>/10</span>
</div>
<div class=”question”></div>
</div>
<div class=”answer-wrapper”></div>
<button class=”btn submit” disabled>Submit Answer</button>
<button class=”btn next hide”>Next Question</button>
</div>
<div class=”end-screen hide”>
<h1 class=”heading”>Quiz Completed!</h1>
<div class=”score”>
<span class=”score-text”>Your score:</span>
<div class=”score-value”>
<span class=”final-score”>0</span>
<span class=”total-score”>/10</span>
</div>
</div>
<button class=”btn restart”>Restart Quiz</button>
</div>
</div>
<script src=”script.js”></script>
</body>
</html>
3. Styling with CSS
To make our quiz app visually appealing, we need to style it using CSS. Here’s the CSS file that will add aesthetics to our HTML structure:
style.css
@import url(‘https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
:root {
— primary-color: #0084ff;
— secondary-color: #05103a;
— text-color: #fff;
— border-color: #3f4868;
— gradient-color: linear-gradient(to right, #ea517c, #b478f1);
— correct-color: #0cef2a;
— wrong-color: #fc3939;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: ‘Poppins’, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #000 url(‘Background.jpg’) no-repeat center center/cover;
}
.container {
width: 100%;
max-width: 400px;
background: rgba(5, 16, 58, 0.11);
padding: 30px;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
}
.heading {
text-align: center;
font-size: 2.5rem;
color: var( — text-color);
margin-bottom: 2rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
label {
display: block;
font-size: 0.875rem;
margin-bottom: 0.5rem;
color: var( — text-color);
}
select {
width: 100%;
padding: 12px;
border: none;
border-radius: 10px;
margin-bottom: 1.25rem;
background: rgba(255, 255, 255, 0.1);
color: var( — text-color);
font-size: 1rem;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
select:focus {
outline: none;
box-shadow: 0 0 0 2px var( — primary-color);
}
/* Styling the dropdown options */
select option {
background-color: rgba(5, 16, 58, 0.986);
color: var( — text-color);
}
.start-screen .btn {
margin-top: 2rem;
}
.hide {
display: none;
}
.timer {
width: 100%;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-bottom: 2rem;
}
.timer .progress {
position: relative;
width: 100%;
height: 40px;
background: transparent;
border-radius: 30px;
overflow: hidden;
margin-bottom: 10px;
border: 3px solid var( — border-color);
}
.timer .progress .progress-bar {
width: 100%;
height: 100%;
border-radius: 30px;
background: var( — gradient-color);
transition: width 1s linear;
}
.timer .progress .progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var( — text-color);
font-size: 1rem;
font-weight: 500;
}
.question-wrapper .number {
color: #a2aace;
font-size: 1.5rem;
font-weight: 500;
margin-bottom: 1.25rem;
}
.question-wrapper .number .total {
color: #576081;
font-size: 1.125rem;
}
.question-wrapper .question {
color: var( — text-color);
font-size: 1.25rem;
font-weight: 500;
margin-bottom: 1.25rem;
line-height: 1.6;
}
.answer-wrapper .answer {
width: 100%;
padding: 15px 20px;
border-radius: 10px;
color: var( — text-color);
border: 3px solid var( — border-color);
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.25rem;
cursor: pointer;
transition: all 0.3s ease;
}
.answer .checkbox {
width: 24px;
height: 24px;
border-radius: 50%;
border: 3px solid var( — border-color);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.answer .checkbox i {
color: var( — text-color);
font-size: 12px;
opacity: 0;
transition: opacity 0.3s ease;
}
.answer:hover:not(.checked),
.answer.selected {
background-color: rgba(12, 128, 239, 0.2);
transform: translateX(5px);
}
.answer:hover:not(.checked) .checkbox,
.answer.selected .checkbox {
background-color: var( — primary-color);
border-color: var( — primary-color);
}
.answer.selected .checkbox i {
opacity: 1;
}
.answer.correct {
border-color: var( — correct-color);
background-color: rgba(12, 239, 42, 0.2);
}
.answer.wrong {
border-color: var( — wrong-color);
background-color: rgba(252, 57, 57, 0.2);
}
.btn {
width: 100%;
height: 60px;
background: var( — primary-color);
border: none;
border-radius: 10px;
color: var( — text-color);
font-size: 1.125rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn:hover {
background: #0a6bc5;
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 132, 255, 0.3);
}
.btn:disabled {
background: #576081;
cursor: not-allowed;
}
.btn.next {
display: none;
}
.end-screen .score {
color: var( — text-color);
font-size: 1.5rem;
font-weight: 500;
margin-bottom: 3rem;
text-align: center;
}
.score .score-text {
color: #a2aace;
font-size: 1rem;
font-weight: 500;
margin-bottom: 1rem;
}
@media (max-width: 468px) {
.container {
min-height: 100vh;
max-width: 100%;
border-radius: 0;
}
}
4. Adding Functionality with JavaScript
Now, let’s add functionality to our app using JavaScript. This script will handle quiz settings, fetching questions from the API, displaying questions, handling user answers, and showing the final score.
script.js
const progressBar = document.querySelector(“.progress-bar”);
const progressText = document.querySelector(“.progress-text”);
const startBtn = document.querySelector(“.start”);
const numQuestions = document.querySelector(“#num-questions”);
const category = document.querySelector(“#category”);
const difficulty = document.querySelector(“#difficulty”);
const timePerQuestion = document.querySelector(“#time”);
const quiz = document.querySelector(“.quiz”);
const startScreen = document.querySelector(“.start-screen”);
const questionText = document.querySelector(“.question”);
const answersWrapper = document.querySelector(“.answer-wrapper”);
const questionNumber = document.querySelector(“.number”);
const submitBtn = document.querySelector(“.submit”);
const nextBtn = document.querySelector(“.next”);
const endScreen = document.querySelector(“.end-screen”);
const finalScore = document.querySelector(“.final-score”);
const totalScore = document.querySelector(“.total-score”);
const restartBtn = document.querySelector(“.restart”);
let questions = [];
let time = 30;
let score = 0;
let currentQuestion;
let timer;
const updateProgress = (value) => {
const percentage = (value / time) * 100;
progressBar.style.width = `${percentage}%`;
progressText.textContent = value;
};
const startQuiz = async () => {
const num = numQuestions.value;
const cat = category.value;
const diff = difficulty.value;
startLoadingAnimation();
try {
const response = await fetch(`https://opentdb.com/api.php?amount=${num}&category=${cat}&difficulty=${diff}&type=multiple`);
const data = await response.json();
questions = data.results;
setTimeout(() => {
startScreen.classList.add(“hide”);
quiz.classList.remove(“hide”);
currentQuestion = 0;
showQuestion(questions[currentQuestion]);
}, 1000);
} catch (error) {
console.error(“Error fetching questions:”, error);
alert(“Failed to load questions. Please try again.”);
}
};
const showQuestion = (question) => {
questionText.innerHTML = question.question;
const answers = […question.incorrect_answers, question.correct_answer];
answers.sort(() => Math.random() — 0.5);
answersWrapper.innerHTML = answers.map(answer => `
<div class=”answer”>
<span class=”text”>${answer}</span>
<span class=”checkbox”>
<i class=”fas fa-check”></i>
</span>
</div>
`).join(“”);
questionNumber.innerHTML = `Question <span class=”current”>${currentQuestion + 1}</span>
<span class=”total”>/${questions.length}</span>`;
addAnswerListeners();
time = parseInt(timePerQuestion.value);
startTimer(time);
};
const addAnswerListeners = () => {
const answersDiv = document.querySelectorAll(“.answer”);
answersDiv.forEach(answer => {
answer.addEventListener(“click”, () => {
if (!answer.classList.contains(“checked”)) {
answersDiv.forEach(a => a.classList.remove(“selected”));
answer.classList.add(“selected”);
submitBtn.disabled = false;
}
});
});
};
const startTimer = (time) => {
clearInterval(timer);
timer = setInterval(() => {
if (time === 3) playAudio(“countdown.mp3”);
if (time >= 0) {
updateProgress(time);
time — ;
} else {
checkAnswer();
}
}, 1000);
};
const startLoadingAnimation = () => {
startBtn.textContent = “Loading”;
const loadingInterval = setInterval(() => {
startBtn.textContent = startBtn.textContent.length === 10 ? “Loading” : startBtn.textContent + “.”;
}, 500);
};
const checkAnswer = () => {
clearInterval(timer);
const selectedAnswer = document.querySelector(“.answer.selected”);
const answers = document.querySelectorAll(“.answer”);
answers.forEach(answer => {
const answerText = answer.querySelector(“.text”).textContent;
if (answerText === questions[currentQuestion].correct_answer) {
answer.classList.add(“correct”);
}
});
if (selectedAnswer) {
const answer = selectedAnswer.querySelector(“.text”).textContent;
if (answer === questions[currentQuestion].correct_answer) {
score++;
} else {
selectedAnswer.classList.add(“wrong”);
}
}
answers.forEach(answer => answer.classList.add(“checked”));
submitBtn.style.display = “none”;
nextBtn.style.display = “block”;
};
const nextQuestion = () => {
currentQuestion++;
if (currentQuestion < questions.length) {
showQuestion(questions[currentQuestion]);
submitBtn.style.display = “block”;
nextBtn.style.display = “none”;
} else {
showScore();
}
};
const showScore = () => {
endScreen.classList.remove(“hide”);
quiz.classList.add(“hide”);
finalScore.textContent = score;
totalScore.textContent = `/ ${questions.length}`;
};
const playAudio = (src) => {
new Audio(src).play();
};
startBtn.addEventListener(“click”, startQuiz);
submitBtn.addEventListener(“click”, checkAnswer);
nextBtn.addEventListener(“click”, nextQuestion);
restartBtn.addEventListener(“click”, () => window.location.reload());
// Remove the defineProperty function as it’s not related to core quiz functionality
5. API Integration and Dynamic Content
In this project, we use the Open Trivia Database API to fetch quiz questions based on user preferences. By making asynchronous requests, we ensure our app remains responsive while loading data.
6. Timer and Progress Bar
To add excitement and challenge, we implemented a countdown timer for each question. The progress bar visually indicates the remaining time, enhancing the user experience.
7. Scoring and Feedback
After each answer, users receive immediate feedback on whether their answer was correct or not. At the end of the quiz, the final score is displayed, providing a sense of accomplishment and encouraging users to try again.
Conclusion
By completing this project, you have learned how to build an interactive quiz application using HTML, CSS, and JavaScript. You have also gained experience in integrating an external API, managing state, and creating a dynamic user interface. This project not only improves your web development skills but also provides a solid foundation for more advanced projects.