JavaScript/Tutorials/Spiele/Multiple-Choice-Quiz

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Multiple Choice oder deutsch Mehrfachauswahl, auch Antwort-Wahl-Verfahren, ist eine in Prüfungen, Tests, Klausuren und Umfragen verwendete Fragetechnik, bei der zu einer Frage mehrere vorformulierte Antworten zur Auswahl stehen. So kann man Sachwissen abfragen, ohne dass die Schreibfähigkeiten (und Rechtschreibung) des Prüfenden ins Spiel kommen.

Dabei ist es zu beachten, dass Multiple Choice im Englischen strikt eine gültige Antwort aus mehreren bedeutet (daher ein falscher Freund[1]), was im Deutschen Single Choice entspricht, während mehrere gültige Antwortmöglichkeiten im Englischen als multiple response bezeichnet werden.[2]

Spielidee

  1. Der Computer gibt eine Frage aus und stellt mehrere Antwortmöglichkeiten.
    • Dabei werden Fragen und die Antwortmöglichkeiten nach dem Zufallsprinzip gemischt.
  2. Der Benutzer kann eine Antwortmöglichkeit durch Klicken auswählen.
  3. Der Computer berechnet am Ende den Prozentsatz oder Anteil der richtig beantworteten Fragen.

HTML

Wenn man etwas aus mehreren Möglichkeiten auswählen soll, bieten sich Radio-Buttons an:

Formular mit Radiobuttons ansehen …
<form id="quiz" action="">
  <fieldset role="radiogroup">
    <legend>Frage …</legend>

    <input type="radio" name="answer" id="a27_0">
    <label for="a27_0">Antwort 1</label>

    <input type="radio" name="answer" id="a27_1">
    <label for="a27_1">Antwort 2</label><

    <input type="radio" name="answer" id="a27_2" value="Antwort 3">
    <label for="a27_2">Antwort 3</label>
  </fieldset>

  <button type="submit">Auswahl bestätigen</button>
</form>

Radio-Buttons (input type="radio") lassen sich über ein gemeinsames name-Attribut miteinander verknüpfen. So kann jeweils nur ein Element ausgewählt werden – klickt man auf ein anderes, wird dieses automatisch selektiert.
Am Ende gibt es einen Absende-Button.

Dabei kann nicht nur mit der Maus auf die Radio-Buttons, sondern auch auf die zugehörigen Labels geklickt werden. Input-Elemente lassen sich auch mit der Tastatur bedienen – bei anderen Elementen müsste diese Funktionalität oft erst mit JavaScript nachgebaut werden. Mit der Tab-Taste navigiert man von einem Element zum nächsten; innerhalb einer Gruppe von Radio-Buttons kann man mit den Pfeiltasten wechseln.
Der Button kann durch Drücken der Enter- oder Leertaste ausgelöst werden

Da ist ja ein Formular – wir wollten doch aber ein Spiel?

Dies ist die semantisch passende Grundstruktur. Das Aussehen kann mit CSS beliebig verändert werden.

CSS

Die Radio-Buttons sehen eher „technisch“ aus. Nun wollen wir sie visuell verstecken und die labels so stylen, dass Nutzaktionen sichtbar werden.

Radio-Buttons ausblenden mit visually-hidden ansehen …
#quiz input {  /*visually-hidden */
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}

Zuerst werden nun die input-Elemente mit den in visually-hidden vorgestellten Techniken so ausgeblendet, dass die labels weiterhin bedienbar sind.

Gestaltung mit CSS ansehen …
    /* Hover-Effekt */
    #quiz label:hover {
      background-color: lightyellow;
    }

    /* mache Fokus sichtbar */
    #quiz input[type="radio"]:focus + label {
      outline: 2px solid black;
      outline-offset: 2px;
    }

    /* Auswahl */
    #quiz input[type="radio"]:checked + label {
      background-color: gold;
      border-color: #866a00;
    }

Dafür erhalten die dazugehörigen Labels nun mit display: block; eine Box, die man anklicken oder mit der Tastatur fokussieren kann.

Diese beiden Zustände werden ebenfalls mit CSS so formatiert, dass man den gewählten Zustand erkennen kann.
input:checked ~ label selektiert mit dem Geschwisterselektor den ausgewählten Radio-Button mit seinem Label.

JavaScript

Im Gegensatz zu den anderen in dieser Reihe besprochenen Spielen werden die Fragen bei einem Multiple-Choice-Quiz im Voraus zusammengestellt und nicht zufällig generiert.

Damit unser Script von Anfang an so flexibel wie möglich ist, sollen die Aufgaben aus einer externen Datei geladen werden.

Datenstruktur

Zunächst sollte überlegt werden, wie man die Fragen und Aufgaben strukturiert – inklusive der richtigen Antwort, falscher Antwortmöglichkeiten sowie einer Erklärung, die nach der Auswertung angezeigt wird. Die Datenstruktur sollte so flexibel gestaltet sein, dass sowohl beliebig viele Aufgaben als auch beliebig viele Antwortmöglichkeiten hinzugefügt werden können. Zudem sollte das Skript später mit mehreren Dat(ei)en unterschiedlicher Inhalte arbeiten können.

Datenstruktur
{
  "category": "IT",
  "question": "Welche Technologien sind clientseitig?",
  "answers": [
    { "correct": true, "answer": "HTML" },
    { "correct": true, "answer": "CSS" },
    { "correct": true, "answer": "JavaScript" },
    { "correct": false, "answer": "PHP" }
  ],
  "explanation": "HTML, CSS und JavaScript - Serverseitig PHP und JavaScript (mit node.js)"
}

Jedes Fragenset besteht aus einer Kategorie, der Frage, einer Liste von Antwortobjekten und einer Erklärung, warum bestimmte Antwortmöglichkeiten falsch oder richtig waren. Dabei können, anders als bei "Wer wird Millionär?" potenziell auch mehrere Optionen richtig sein – auch wenn unser Script das noch nicht berücksichtigen wird.

Ein solches Antwortobjekt besteht nun aus …

  • einem Schalter correct, der den Wert true/false haben kann.
  • der answer und einer
  • Option für weitere Antwortattribute, wie z.B. Medienlinks.

Bei der Hauptstadtfrage könnte es so aussehen:

Datenstruktur ansehen …
{
  "data": [
    {
      "category": "Politik",
      "question": "Welche Stadt wurde 1990 deutsche Hauptstadt?",
      "answers": [
        {
          "correct": true,
          "answer": "Berlin"
        },
        {
          "correct": false,
          "answer": "Frankfurt am Main"
        },
        {
          "correct": false,
          "answer": "Bonn"
        },
        {
          "correct": false,
          "answer": "München"
        }
      ],
      "explanation": "Berlin wurde am 03.10.1990 auf Grund des Einigungsvertrages wieder zur deutschen Bundeshauptstadt.<br>Frankfurt war 1949 als Bundeshauptstadt im Rennen,<br>Bonn setzte sich am 10.05.1949 gegen Frankfurt als Bundeshauptstadt durch, bis es am 03.10.1990 abgelöst wurde."
    },
    {
      "category": "Biologie",
      "question": "Erdbeeren sind …",
      "answers": [
        {
          "correct": true,
          "answer": "Nüsse"
        },
        {
          "correct": false,
          "answer": "Beeren"
        },
        {
          "correct": false,
          "answer": "Obst"
        },
        {
          "correct": false,
          "answer": "Gemüse"
        }
      ],
      "explanation": "In Wahrheit gehört die Erdbeere zu den Sammelnussfrüchten. Die vermeintlich rote Frucht ist in Wahrheit nur eine Scheinfrucht, auch Fruchtboden genannt."
    }
  ]
}

Diese JSON-Datei können wir nun in unser Script und unsere Webseite laden.

Auch wenn dies bei einem Quiz kompliziert erscheint, ermöglicht es diese Vorgehensweise,
das selbe Script für mehrere Quizze zu verwenden.

Laden externer JSON-Dateien

Wie oben erklärt, trennen wir die Daten (im JSON-Objekt) von der Quiz-Logik. Sobald das Quiz startet, wird das externe JSON geladen:

Laden des Templates ansehen …
document.addEventListener("DOMContentLoaded", function () {
  const JSONUrl = "/extensions/Selfhtml/example.php/Beispiel:Multiple-choice-questions.json";	
  ...

  async function loadQuestions() {
    try {
      const response = await fetch(JSONUrl);
      const data = await response.json();
      questions = data.data;
    } catch (error) {
      console.error("Fehler beim Laden der Fragen:", error);
    }
  }

In einem try ..catch-Block, um Fehler abzufangen, wird nun ein GET REQUEST an die angegeben URL gesendet.[3]

  • fetch liefert einen Promise mit der Antwort.
    Durch das Schlüsselwort await wird das Script pausiert, bis die Antwort (Repsonse) erfolgt ist
  • const data = await response.json(); liest den Inhalt der Antwort aus und parst es als JSON.
    Diese ist nun im data-Objekt verfügbar.


Initialisierung

Unsere Multiple-Choice-Aufgaben sind in einem data-Objekt - das unterschiedlich viele Aufgaben- und Antwort­möglichkeiten umfassen kann. Deshalb können wir kein festes HTML-Markup verwenden, sondern verfolgen drei mögliche Ansätze:

  1. Erzeugen der Elemente mit createElement
  2. Einfügen von HTML-Markup mit insertAdjacentHTML
  3. Einfügen eines HTML-Templates mit appendChild
Laden des Templates ansehen …
  function renderQuestion(index) {
    const questionData = questions[index];

    const oldFieldset = form.querySelector("fieldset");
    if (oldFieldset) oldFieldset.remove();

    const quizContent = template.content.cloneNode(true);
    const fieldset = quizContent.querySelector("fieldset");
    const legend = fieldset.querySelector("legend");

    legend.textContent = `${questionData.category}: ${questionData.question}`;

    fieldset.innerHTML = `<legend>${legend.textContent}</legend>`; 

    questionData.answers.forEach((answer, i) => {
      const inputId = `q${index}_a${i}`;

      const input = document.createElement("input");
      input.type = "radio";
      input.name = `q${index}`;
      input.id = inputId;
      input.value = answer.answer;

      const label = document.createElement("label");
      label.htmlFor = inputId;
      label.textContent = answer.answer;

      fieldset.appendChild(input);
      fieldset.appendChild(label);
    });

    form.insertBefore(fieldset, button);
  }

Ich habe mich für eine Mischung entschieden:

  • const quizContent = template.content.cloneNode(true); lädt das template mit dem fieldset und der legend
  • questionData.answers.forEach((answer, i) => { }; durchläuft alle Antworten und
    • erzeugt mit document.createElement("input"); ein neues input-Element mit passendem label

So ist man flexibel, wenn es einmal nur eine true/false-Frage oder drei Antwortmöglichkeiten gibt.

Auswertung

Sobald unser Script die Aufgabe geladen hat, warten wir auf eine Eingabe durch die Person vor dem Bildschirm, die dann vom Browser ausgewertet wird. Danach soll eine neue Aufgabe gestellt und zum Schluss das Ergebnis ausgegeben werden.

EVA-Prinzip ansehen …
 function handleSubmit() { 
   const selected = form.querySelector(`input[name="q${currentQuestionIndex}"]:checked`);
   if (!selected) {
     alertBox("Bitte wählen Sie eine Antwort aus!", "error");
     return;
   }

   const question = questions[currentQuestionIndex];
   const selectedValue = selected.value;
   const selectedAnswerObj = question.answers.find(a => a.answer === selectedValue);
   const correctAnswer = question.answers.find(a => a.correct);

   const isCorrect = selectedAnswerObj?.correct;

   // Store result
   results.push({
     question: question.question,
     userAnswer: selectedValue,
     isCorrect,
     correctAnswer: correctAnswer.answer,
     explanation: question.explanation || "",
   });

   currentQuestionIndex++;

   if (currentQuestionIndex < questions.length) {
     setTimeout(() => {
       renderQuestion(currentQuestionIndex);
     }, 500);
   } else {
     form.remove(); // remove quiz form
     renderResults();
   }
 }


Ergebnisausgabe ansehen …
 function renderResults() {
   const resultList = document.createElement("ol");
   resultList.id = "result";

   results.forEach(result => {
     const li = document.createElement("li");

     const questionP = document.createElement("p");
     questionP.className = "question";
     questionP.textContent = result.question;

     const answerP = document.createElement("p");
     answerP.innerHTML = `Ihre Antwort: ${result.userAnswer}`;

     const explanationP = document.createElement("p");
     explanationP.innerHTML = `Erläuterung: ${result.explanation}`;

     li.appendChild(questionP);
     li.appendChild(answerP);
     li.appendChild(explanationP);

     resultList.appendChild(li);
   });

Nun wird eine sortierte Liste erzeugt, in der jede Frage und die gewählte Antwort, sowie die richtige Lösung und eine Erklärung gezeigt werden.

Ergebnis: Dies ist ein Script, mit dem z. B. über ein Auswahlmenü mehrere Quizze geladen werden können, ohne das eigentliche Script anfassen und verändern zu müssen. Dabei ist es völlig responsiv und auch auf mobilen Geräten zu nutzen.

Anwendungsbeispiele

Mithilfe dieses Scripts haben wir zum Jubiläum 2025 ein aktuelles Rätsel erstellt sowie das Quiz zum 10-jährigen Jubiläum wieder zum Leben erweckt:

  • 10 Jahre SELFHTML
    1995-2005
  • Weihnachtsquiz
    2016
  • 30 Jahre SELFHTML
    1995-2025

Das Quiz-Script ist aus Gründen der einfacheren Handhabung des Weihnachts-Quiz 2016 im HTML-Dokument integriert. Man kann es im Quellcode des Dokuments betrachten und mit dem Seiteninspektor analysieren.



Weblinks

  1. falscher Freund (Wikipedia)
  2. Felix Riesterer, der Autor dieses Scripts ist seit 2005 bei SELFHTML aktiv. Er hat unter anderem ein JavaScript-Framework für interaktive Lernaufgaben geschrieben, dass unter einer GNU Lesser General Public License (LGPL) verfügbar ist. Mit diesem können mit einfachsten Mitteln diverse Quizze auf HTML-Seiten erstellt werden.
  3. Arbeiten mit JSON(developer.mozilla.org)