JavaScript를 사용하여 퀴즈 애플리케이션 구축
89181 단어 codenewbiewebdevtutorialjavascript
사양 정의
테스트는 두 개의 주요 교실로 나눌 것이다.첫 번째는 구역을 설정하는 것입니다. 유저는 그 중에서 난이도, 분류와 그가 대답하고 싶은 질문 수량을 선택할 수 있습니다.이를 위해, 우리는 모든 정보를 추적할 설정 클래스를 만들 것입니다.다 한 후에 그는 테스트를 시작할 수 있다.
두 번째 분야는 테스트다.테스트 클래스는 유저의 진도를 추적하고 최종 화면에 다음 문제를 표시할지 결정합니다.
그 밖에 퀴즈 클래스에는 또 다른 두 개의 구성 요소가 있다. 첫 번째는 일련의 문제 클래스이다. 이런 문제 클래스는 문제의 데이터를 저장하고 문제를 표시하며 답안이 정확한지 확인한다.다른 하나는 마지막 클래스입니다. 마지막 페이지를 표시합니다. 플레이어의 점수를 포함합니다.
우리는 Open Trivia DB API를 사용하여 문제를 왔다 갔다 대답할 것이다. 이렇게 하면 우리는 자신의 문제를 제기할 필요가 없다.
내가 클래스에 관한 많은 내용을 말했기 때문에 우리는 Object-Oriented-Programming 이 테스트 응용 프로그램을 실현하기 위해 Functional-Programming 를 사용할 것이다.만약 당신이 이 두 가지 모델 사이의 차이에 흥미가 있다면, 나의 문장을 보십시오.
선결 조건
퀴즈를 구현하기 전에 폴더 구조와 HTML 및 CSS를 작성해야 합니다.이 문서에서는 응용 프로그램의 JavaScript 섹션에 중점을 둡니다.따라서 나는 이 절에서 필요한 HTML과 CSS를 제공할 것이다.다음 폴더 구조를 만드는 것부터 시작합니다.
$ mkdir vanilla-quiz
$ cd ./vanilla-quiz
$ mkdir quiz
$ touch index.html index.js styles.css
색인을 복사하여 붙여넣습니다.html와 스타일.css:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vanilla Quiz</title>
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Questrial&display=swap" rel="stylesheet">
</head>
<body>
<main>
<div class="header">
<h2>Vanilla Quiz</h2>
</div>
<div class="main">
<div class="final">
<h3>You answered all of the questions!</h3>
<p>Score: </p><p class="score"></p>
<h4>Want to try it again?</h4>
<button id="again" class="submit">Again</button>
</div>
<div class="quiz">
<div class="count">
<p class="current">0</p><p style="margin-left:40px"> / </p><p class="total"></p>
</div>
<h3 id="question"></h3>
<label id="a1" class="container">
<input type="radio" checked="checked" name="radio">
<span class="checkmark"></span>
</label>
<label id="a2" class="container">
<input type="radio" name="radio">
<span class="checkmark"></span>
</label>
<label id="a3" class="container">
<input type="radio" name="radio">
<span class="checkmark"></span>
</label>
<label id="a4" class="container">
<input type="radio" name="radio">
<span class="checkmark"></span>
</label>
<button id="next" class="submit">Submit</button>
</div>
<div class="settings">
<h3 style="text-align: center;">Set up your Quiz!</h3>
<label for="category">Category</label>
<select name="category" id="category">
<option value="9">General Knowledge</option>
<option value="27">Animals</option>
<option value="15">Video Games</option>
<option value="23">History</option>
<option value="21">Sports</option>
</select>
<div class="mt30">
<label for="difficulty">Difficulty</label>
<label class="container" style="display: inline; margin-left: 30px;">Easy
<input type="radio" name="radio" id="easy">
<span class="checkmark" style="margin-top: 2px;"></span>
</label>
<label class="container" style="display: inline; margin-left: 30px;">Medium
<input type="radio" name="radio" id="medium">
<span class="checkmark" style="margin-top: 2px;"></span>
</label>
<label class="container" style="display: inline; margin-left: 30px;">Hard
<input type="radio" name="radio" id="hard">
<span class="checkmark" style="margin-top: 2px;"></span>
</label>
</div>
<div class="mt30">
<label for="questions">Number of questions</label>
<input name="questions" id="questions" type="text" pattern="[0-9]*" />
</div>
<button id="start" class="submit">Start</button>
</div>
</div>
</main>
<script type="module" src="index.js"></script>
</body>
</html>
:root {
--primary-color: #5D737E;
--secondary-color: #D6F8D6;
--tertiary-color: #7FC6A4;
--quaternary-color: #55505C;
--hover-color: #4e616b;
--shadow-color:rgba(57, 127, 93, 0.4);
--font-style: 'Questrial';
}
body {
font-family: var(--font-style), 'Ranchers', cursive;
background-color: var(--secondary-color);
width: 100vw;
height: 100vh;
justify-content: center;
align-items: center;
}
h2 {
font-size: 3.5rem;
text-align: center;
color: var(--primary-color);
}
.mt30 {
margin-top: 30px;
}
.header {
padding: 15px;
}
.main {
display: flex;
justify-content: center;
}
.settings {
z-index: 1;
}
.final {
visibility: hidden;
z-index: 2;
}
.final p {
font-size: 30px;
text-align: center;
}
.final h4 {
font-size: 33px;
text-align: center;
}
.quiz {
visibility: hidden;
z-index: 0;
}
#questions {
font-size: 20px;
font-family: var(--font-style), 'Ranchers', cursive;
font-weight: 600;
line-height: 1.3;
color: white;
background-color: var(--primary-color);
appearance: none;
border: none;
padding: 5px;
border-radius: 5px;
margin-left: 30px;
outline: none;
text-align: center;
width: 120px;
}
.settings select {
font-size: 20px;
font-family: var(--font-style), 'Ranchers', cursive;
font-weight: 600;
line-height: 1.3;
letter-spacing: 1px;
color: white;
background-color: var(--primary-color);
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
border: none;
padding: 5px;
border-radius: 5px;
margin-left: 20px;
outline: none;
text-align: center;
}
.settings select::-ms-expand {
display: none;
}
.settings select:hover {
border-color: var(--hover-color);
}
.settings select:focus {
border-color: var(--hover-color);
}
.settings select option {
/* font-weight: bolder; */
font-family: var(--font-style), 'Ranchers', sans-serif;
}
.settings label {
font-size: 25px;
margin-right: 16px;
}
.quiz, .settings, .final {
position: absolute;
padding: 0px 35px 35px 35px;
max-width: 560px;
background-color: var(--tertiary-color);
border-radius: 7px;
-webkit-box-shadow: 10px 10px 3px -4px var(--shadow-color);
-moz-box-shadow: 10px 10px 3px -4px var(--shadow-color);
box-shadow: 10px 10px 5px -4px var(--shadow-color);
}
h3 {
display: block;
width: 550px;
font-size: 35px;
font-weight: 350;
word-wrap: break-word;
}
.submit {
width: 100%;
color: white;
background-color: var(--primary-color);
font-family: var(--font-style), 'Ranchers', cursive;
outline: none;
border: none;
height: 50px;
font-size: 1.8rem;
margin-top: 20px;
border-radius: 5px;
letter-spacing: 2px;
}
.submit:hover {
background-color: var(--hover-color);
cursor: pointer;
color: #FAF33E;
}
/* The container */
.count {
display: block;
left: 75%;
position: relative;
padding-left: 35px;
margin-bottom: 100px;
cursor: pointer;
}
.count p {
position: absolute;
font-size: 35px;
}
.total {
margin-left: 50px;
}
/* The container */
.container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 25px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Hide the browser's default radio button */
.container input {
position: absolute;
opacity: 0;
cursor: pointer;
}
/* Create a custom radio button */
.checkmark {
position: absolute;
top: -2px;
left: 0px;
height: 25px;
width: 25px;
background-color: white;
border-radius: 30%;
}
/* On mouse-over, add a grey background color */
.container:hover input ~ .checkmark {
background-color: #FAF33E;
}
/* When the radio button is checked, add a blue background */
.container input:checked ~ .checkmark {
background-color: var(--quaternary-color);
}
/* Create the indicator (the dot/circle - hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the indicator (dot/circle) when checked */
.container input:checked ~ .checkmark:after {
display: block;
}
현재, 우리는 우리의 응용 프로그램을 개발하기 시작할 준비를 하고 있다.우리가 배울 첫 수업은 설정이다.어떻게 문제를 얻습니까?
클래스를 설정하는 목표는 유저가 그들의 클래스, 난이도와 그들이 대답하고 싶은 질문 수량을 선택할 수 있다는 것이다.우리는 이 세 가지 매개 변수 중에서 플레이어의 문제를 얻기 위해 개방된 자질구레한 DB API에 대한 요청을 만들어야 한다.
클래스를 만들기 전에, 색인에 실례를 만들어야 합니다.js는 다음과 같습니다.
import Settings from ‘./quiz/settings.js’;
new Settings();
파일 설정이 올바르지 않기 때문에 오류가 발생할 수 있습니다.js가 아직 존재하지 않기 때문에 계속 만듭니다.$ touch ./quiz/settings.js
다음은 설정 클래스에 대한 뼈대를 만듭니다.이를 위해, 우리는 구조 함수와 start 퀴즈 방법, 그리고 export 문장을 가진 클래스가 필요하다.export 문장이 없으면, 색인에서 클래스를 가져올 수 없습니다.js.그것은 마땅히 이렇게 해야 한다.class Settings {
constructor() {
}
startQuiz() {
}
}
export default Settings;
구조 함수에서 테스트를 시작하는 데 필요한 모든 DOM 요소를 원합니다.이를 위해, 유저가 테스트를 시작할 때 가시성을 전환할 수 있도록 두 개의 구역, 테스트와 설정을 캡처해야 합니다.문제를 가져오는 요청을 만들기 위해 모든 파라미터가 필요합니다.마지막으로 가장 중요하지 않은 것은 start Quiz 방법을 클릭 이벤트에 추가하기 위해 단추를 가져와야 한다는 것이다.constructor() {
this.quizElement = document.querySelector('.quiz');
this.settingsElement = document.querySelector('.settings');
this.category = document.querySelector('#category');
this.numberOfQuestions = document.querySelector('#questions');
this.difficulty = [
document.querySelector('#easy'),
document.querySelector('#medium'),
document.querySelector('#hard'),
];
this.startButton = document.querySelector('#start');
this.quiz = { };
this.startButton.addEventListener('click', this.startQuiz.bind(this));
}
첫 번째 단락에서 우리는 모든 DOM 요소를 얻었습니다. 이 요소들은 나중에 필터링할 수 있도록 하나의 그룹에 저장됩니다.그 다음에 퀵 속성을 초기화하고 start 퀴즈 방법을 start Button에 추가합니다.start Quiz 방법에 연결하십시오.만약 네가 이렇게 하지 않는다면, 너는 방법에서 그것을 사용할 수 없을 것이다.테스트를 시작하기 위해서는 모든 파라미터를 수집하고 동적 생성 요청을 해야 합니다.우리는 API 호출을 처리하고 있기 때문에, 나는 async/await를 사용하여 비동기 호출을 처리하기로 결정했다.오류가 없는지 확인하기 위해서, 우리는 모든 호출을try-catch 블록에 포장할 것입니다.그래서 start 퀴즈 방법은 다음과 같다.
async startQuiz() {
try {
const amount = this.getAmount();
const categoryId = this.category.value;
const difficulty = this.getCurrentDifficulty();
const url = `https://opentdb.com/api.php?amount=${amount}&category=${categoryId}&difficulty=${difficulty}&type=multiple`;
let data = await this.fetchData(url);
this.toggleVisibility();
this.quiz = new Quiz(this.quizElement, amount, data.results);
} catch (error) {
alert(error);
}
}
우리 여기서 뭐 해요?우선, 우리는 이 세 가지 값을 얻었다. 왜냐하면 우리가 사용하는 방법의 수량과 난이도가 아직 실현되지 않았기 때문이다.이러한 방법에서, 우리는 어떤 난이도를 선택하지 않거나 음수를 문제수로 입력하지 않는 등 오류를 처리할 것이다.
그리고 방금 얻은 매개 변수로 URL을 만듭니다.이 URL은 fetchData 메서드에서 전달됩니다. 이 메서드는 요청을 보내고 데이터를 되돌려줍니다.그 다음에 우리는 toggle Visibility를 호출하고 결과, 수량, 테스트 요소를 전달함으로써 새로운 테스트 대상을 초기화합니다.
만약 언제든지 오류가 발생하면, 우리는 그것을 포착하고 경보 방법으로 표시할 것이다.
최종 설정 클래스는 다음과 같습니다.
import Quiz from './quiz.js';
class Settings {
constructor() {
this.quizElement = document.querySelector('.quiz');
this.settingsElement = document.querySelector('.settings');
this.category = document.querySelector('#category');
this.numberOfQuestions = document.querySelector('#questions');
this.difficulty = [
document.querySelector('#easy'),
document.querySelector('#medium'),
document.querySelector('#hard'),
];
this.startButton = document.querySelector('#start');
this.quiz = { };
this.startButton.addEventListener('click', this.startQuiz.bind(this));
}
async startQuiz() {
try {
const amount = this.getAmount();
const categoryId = this.category.value;
const difficulty = this.getCurrentDifficulty();
const url = `https://opentdb.com/api.php?amount=${amount}&category=${categoryId}&difficulty=${difficulty}&type=multiple`;
let data = await this.fetchData(url);
this.toggleVisibility();
this.quiz = new Quiz(this.quizElement, amount, data.results);
} catch (error) {
alert(error);
}
}
toggleVisibility() {
this.settingsElement.style.visibility = 'hidden';
this.quizElement.style.visibility = 'visible';
}
async fetchData(url) {
const response = await fetch(url);
const result = await response.json();
return result;
}
getCurrentDifficulty() {
const checkedDifficulty = this.difficulty.filter(element => element.checked);
if (checkedDifficulty.length === 1) {
return checkedDifficulty[0].id;
} else {
throw new Error('Please select a difficulty!');
}
}
getAmount() {
const amount = this.numberOfQuestions.value;
// Not negative, not 0 and not over 50
if (amount > 0 && amount < 51) {
return amount;
}
throw new Error('Please enter a number of questions between 1 and 50!');
}
}
export default Settings;
만약 유저가 어떤 내용을 선택하지 않았거나 선택한 값이 경계를 초과하지 않았다면, get Amount와 GetCurrentStudious 두 가지 방법은 모두 오류를 되돌려줍니다.우리는 이 파일의 맨 위에 테스트 클래스의 가져오기 문장을 추가했다.다른 두 가지 방법(fetchData와 toggleVisibility)은 완전히 그들의 이름에 따라 실행된다.이제 우리는 다음 문답 수업에 주의력을 집중할 수 있다.테스트 시간 다 됐어!
퀴즈 클래스를 고려하기 전에 파일이 포함된 파일을 만들어야 합니다.
$ touch ./quiz/quiz.js
우리는 설정부터 시작한다.뼈대를 생성합니다.class Quiz {
constructor(quizElement, amount, questions) {
this.quizElement = quizElement;
this.totalAmount = amount;
this.questions = this.setQuestions(questions);
}
setQuestions(questions) {
return questions.map(question => new Question(question));
}
nextQuestion() {
}
endQuiz() {
}
}
export default Settings;
이번에는 설정 대상이 전송하는 매개 변수를 처리해야 합니다.문제에 대해 우리는 설정 대상이 전송하는 모든 문제에 대해 문제 대상을 만듭니다.구조기는 더 많은 설정을 필요로 하기 때문에 다음 단추에 더 많은 DOM 요소와 이벤트 탐지기를 추가할 것입니다.이거 계속 하자!constructor(quizElement, amount, questions) {
this.quizElement = quizElement;
this.currentElement = document.querySelector('.current');
this.totalElement = document.querySelector('.total');
this.nextButton = document.querySelector('#next');
this.finalElement = document.querySelector('.final')
this.totalAmount = amount;
this.answeredAmount = 0;
this.questions = this.setQuestions(questions);
this.nextButton.addEventListener('click',
this.nextQuestion.bind(this));
this.renderQuestion();
}
보시다시피 설정된 구조 함수처럼 보입니다.js.하나의 주요 차이점은 마지막에 RenderQuestion을 호출하는 것이다.이번 호출의 목적은 우리가 즉시 첫 번째 문제를 나타내기를 바라는 것이다.setQuestions와nextQuestion 사이에서 방법renderQuestion을 만들고 다음과 같이 실현합니다.
renderQuestion() {
this.questions[this.answeredAmount].render();
this.currentElement.innerHTML = this.answeredAmount;
this.totalElement.innerHTML = this.totalAmount;
}
테스트를 시작할 때 answered Amount는 0이기 때문에 우리는 문제 그룹의 첫 번째 문제에 대해render 방법을 호출합니다.이후에 우리는 플레이어의 현재 진도를 설정합니다.우리는 아직 문제 유형을 실현하지 못했기 때문에, 이 코드가 오류를 일으켰지만, 우리는 곧 이 문제를 해결할 것이다.nextQuestion 방법을 구현해 보겠습니다.이를 위해, 만약 유저가 답을 선택했다면, 만약 그렇다면, 어떤 답을 선택했는지.다음으로, 우리는 결과를 유저에게 표시하고answeredAmount를 1 증가시켜야 합니다.마지막으로, 우리는 또 다른 문제가 있는지 확인해야 합니다. 만약 있다면, 보여 주십시오.만약 이것이 마지막이라면, 우리는 결과 화면에 들어가야 한다.
nextQuestion() {
const checkedElement = this.questions[this.answeredAmount].answerElements.filter(el => el.firstChild.checked);
if (checkedElement.length === 0) {
alert(‘You need to select an answer’);
} else {
this.questions[this.answeredAmount].answer(checkedElement)
this.showResult();
this.answeredAmount++;
(this.answeredAmount < this.totalAmount) ? this.renderQuestion() : this.endQuiz();
}
}
이 클래스에서 유일하게 부족한 방법은 showResult, EndQuike와 결과 화면의 모든 정답을 정리하는 방법이다.마지막 퀴즈.js는 다음과 같이 해야 합니다.import Final from './final.js';
import Question from './question.js'
class Quiz {
constructor(quizElement, amount, questions) {
this.quizElement = quizElement;
this.currentElement = document.querySelector('.current');
this.totalElement = document.querySelector('.total');
this.nextButton = document.querySelector('#next');
this.finalElement = document.querySelector('.final')
this.totalAmount = amount;
this.answeredAmount = 0;
this.questions = this.setQuestions(questions);
this.nextButton.addEventListener('click', this.nextQuestion.bind(this));
this.renderQuestion();
}
setQuestions(questions) {
return questions.map(question => new Question(question));
}
renderQuestion() {
this.questions[this.answeredAmount].render();
this.currentElement.innerHTML = this.answeredAmount;
this.totalElement.innerHTML = this.totalAmount;
}
nextQuestion() {
const checkedElement = this.questions[this.answeredAmount].answerElements.filter(el => el.firstChild.checked);
if (checkedElement.length === 0) {
alert('You need to select an answer');
} else {
this.questions[this.answeredAmount].answer(checkedElement)
this.showResult();
this.answeredAmount++;
(this.answeredAmount < this.totalAmount) ? this.renderQuestion() : this.endQuiz();
}
}
showResult() {
this.questions[this.answeredAmount].isCorrect ? alert('Correct answer :)') : alert('Wrong answer :(');
}
endQuiz() {
this.quizElement.style.visibility = 'hidden';
this.finalElement.style.visibility = 'visible';
const correctAnswersTotal = this.calculateCorrectAnswers();
this.final = new Final(correctAnswersTotal, this.totalAmount);
}
calculateCorrectAnswers() {
let count = 0;
this.questions.forEach(el => {
if (el.isCorrect) {
count++;
}
});
return count;
}
}
export default Quiz;
우리는 토론하기 위해 상단에 이 두 가지 수입 제품을 추가했다.js와 최종 버전.js.이 밖에 우리는 ternary operator 질문에 정확하게 대답했는지 검사를 통해 showResult를 실현했다.Endquick 방법은 우리가 설정한 toggle Visibility 방법과 비슷해 보입니다.js, 이것은 calculate Correct Answers를 호출하여 최종 클래스의 새로운 실례에 전달하는 것을 제외하고는 모든 정답을 정리해야 합니다.
문제 및 결과 표시
우리의 테스트 클래스는 현재 작용하지 않는다. 왜냐하면 아직 두 개의 의존항이 존재하지 않기 때문이다.다음 두 파일을 추가하여 변경합니다.
$ touch ./quiz/question.js ./quiz/final.js
우리는 문제 유형을 실현하는 것부터 시작한다.먼저 다음과 같이 파일에 뼈대를 추가합니다.class Question {
constructor(question) {
this.correctAnswer = question.correct_answer;
this.question = question.question;
this.answers = this.shuffleAnswers([
question.correct_answer,
...question.incorrect_answers
]);
}
shuffleAnswers(answers) {
}
answer(checkedElement) {
}
render() {
}
}
export default Question;
그럼 우리 여기서 뭐 했어요?우리는 문제, 정답, 그리고 일련의 답안을 저장하고 저장하기 전에 카드를 씻는다.
다음 단계는 shuffleAnswers,answer,render 방법을 실현하는 것입니다.수조의 카드 세척에 대해 우리는 사용할 것이다Fisher-Yates-Shuffle-Algorithm.
answer 방법은 유저의 선택과correctAnswer 속성을 비교할 뿐,render 방법은 문제와 가능한 모든 답을 표시합니다.이를 실현하기 위해서는 그에 상응하는 DOM 요소를 얻고 최종적으로 이 문제를 해결해야 한다.js:
class Question {
constructor(question) {
this.questionElement = document.querySelector('#question');
this.answerElements = [
document.querySelector('#a1'),
document.querySelector('#a2'),
document.querySelector('#a3'),
document.querySelector('#a4'),
];
this.correctAnswer = question.correct_answer;
this.question = question.question;
this.isCorrect = false;
this.answers = this.shuffleAnswers([
question.correct_answer,
...question.incorrect_answers
]);
}
shuffleAnswers(answers) {
for (let i = answers.length - 1; i > 0; i--){
const j = Math.floor(Math.random() * i)
const temp = answers[i]
answers[i] = answers[j]
answers[j] = temp
}
return answers;
}
answer(checkedElement) {
this.isCorrect = (checkedElement[0].textContent === this.correctAnswer) ? true : false;
}
render() {
this.questionElement.innerHTML = this.question;
this.answerElements.forEach((el, index) => {
el.innerHTML = '<input type="radio" name="radio"><span class="checkmark"></span>' + this.answers[index];
});
}
}
export default Question;
현재 유일하게 부족한 것은 마지막 수업이다.이 종류는 매우 간단하다. 우리는 플레이어에 최종 결과를 표시하기 위해 DOM 요소를 얻기만 하면 된다.편의성을 높이기 위해서, 유저가 다시 시작할 수 있도록 페이지를 다시 불러오는 단추를 추가할 수 있습니다.그것은 마땅히 이렇게 해야 한다.class Final {
constructor(count, totalAmount) {
this.scoreElement = document.querySelector('.score');
this.againButton = document.querySelector('#again');
this.render(count, totalAmount);
this.againButton.addEventListener('click', location.reload.bind(location));
}
render(count, totalAmount) {
this.scoreElement.innerHTML = `You answered ${count} out of ${totalAmount} correct!`;
}
}
export default Final;
결론
테스트 응용 프로그램이 이미 완성되었다.우리는 간단한 낡은 자바스크립트를 사용하여 이 점을 실현하고 대상을 대상으로 프로그래밍하는 개념을 사용했다.나는 네가 이것을 좋아하길 바란다. 평소와 같이, 너는 나의 GitHub에서 코드를 찾을 수 있기를 바란다.
화면 캡처
유저는 분류, 난이도, 그리고 그들이 대답하고 싶은 질문 수량을 선택한 후 테스트를 시작할 수 있습니다.
처음에는 여러 가지 선택 문제가 이렇게 나왔다.
모든 문제의 마지막에 너는 이 마지막 페이지를 볼 수 있다.
나는 네가 나를 따라 즐겁게 놀기를 바란다.만약 네가 원한다면, 이 판본을 개선해 보아라.
사진은 Emily Morter 에서 Unsplash
Reference
이 문제에 관하여(JavaScript를 사용하여 퀴즈 애플리케이션 구축), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/klamserdev/building-a-quiz-app-in-javascript-15lh텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)