Projektziel: Die Lernkartei ist ein Vokabeltrainer im Browser. Man wählt ein Sprachpaket, die Karten werden gemischt und lassen sich per Klick umdrehen: Vorne steht das deutsche Wort mit einem Beispielsatz, hinten die Übersetzung mit Beispielsatz. Es gibt drei Sprachpakete: Deutsch - Englisch, Deutsch - Französisch und Französisch - Grammatik.

Die erste Version (August 2024) war eine einzelne PHP-Datei. Die Anwendung wurde inzwischen neu aufgebaut und ist am 20.09.2026 von micha-it.de auf diese Seite umgezogen. Dieser Beitrag beschreibt die neue Version.

Lernkartei mit Sprachpaket Deutsch - Englisch, zwei Karten sind umgedreht
Bild 1 - Lernkartei mit Sprachpaket Deutsch - Englisch, zwei Karten sind umgedreht

Projektlink: Lernkartei

Was sich gegenüber der ersten Version geändert hat

  • Drei Dateien statt einer: lernkartei.php (Aufbau der Seite), lernkartei.css (Darstellung) und lernkartei.js (Logik).
  • Jede Karte ist eine Schaltfläche (button). Dadurch lassen sich die Karten auch mit der Tastatur bedienen, und aria-pressed zeigt an, ob eine Karte umgedreht ist.
  • Beispielsätze auf Vorder- und Rückseite.
  • "Neu mischen" bringt die Karten in eine zufällige Reihenfolge, eine Statuszeile zeigt den Fortschritt (zum Beispiel "2 / 70 aufgedeckt").
  • Das zuletzt gewählte Sprachpaket wird im Browser gemerkt (localStorage).
  • Kann ein Sprachpaket nicht geladen werden, erscheint eine Fehlermeldung.
  • Neu: das dritte Sprachpaket Französisch - Grammatik.

Sprachpakete als JSON

Jedes Sprachpaket ist eine JSON-Datei mit einer Liste von Karten. Jede Karte hat das deutsche Wort (de), die Übersetzung (en oder fr) und je einen Beispielsatz (deEx, enEx oder frEx). Ein neues Sprachpaket ist damit nur eine weitere Datei.


// data1.json (Deutsch - Englisch)
{
  "id": 0,
  "de": "das Haus",
  "en": "house",
  "deEx": "Das Haus ist groß.",
  "enEx": "The house is big."
}

// data2.json (Deutsch - Französisch)
{
  "id": 1,
  "de": "der Baum",
  "fr": "l'arbre",
  "deEx": "Der Baum ist hoch.",
  "frEx": "L'arbre est haut."
}

Der JavaScript-Teil

Welche Felder gehören zu welchem Sprachpaket:

getLanguageMeta liefert zur gewählten Datei die Beschriftung der Rückseite und die Namen der Felder, aus denen Übersetzung und Beispielsatz gelesen werden.


function getLanguageMeta(fileName) {
   if (fileName === 'data3.json') {
      return { label: 'Französisch - Grammatik', key: 'fr', exampleKey: 'frEx' };
   }

   if (fileName === 'data2.json') {
      return { label: 'Deutsch - Französisch', key: 'fr', exampleKey: 'frEx' };
   }

   return { label: 'Deutsch - Englisch', key: 'en', exampleKey: 'enEx' };
}

Karten mischen:

Das Mischen folgt dem Fisher-Yates-Verfahren: Von hinten nach vorne wird jede Karte mit einer zufällig gewählten Karte aus dem Bereich bis zu ihrer eigenen Position getauscht. Das Original-Array bleibt unverändert, gemischt wird eine Kopie.


function shuffleCards(cards) {
   const shuffled = [...cards];

   for (let index = shuffled.length - 1; index > 0; index -= 1) {
      const randomIndex = Math.floor(Math.random() * (index + 1));
      [shuffled[index], shuffled[randomIndex]] = [shuffled[randomIndex], shuffled[index]];
   }

   return shuffled;
}

Eine Karte erzeugen:

Die Karte besteht aus einer Schaltfläche mit Vorder- und Rückseite. Ein Klick schaltet die Klasse is-flipped um, das CSS dreht die Karte, und der Fortschritt wird neu berechnet.


function createCardElement(cardData, meta) {
   const article = document.createElement('article');
   article.className = 'flashcard';
   article.dataset.id = String(cardData.id);

   const button = document.createElement('button');
   button.type = 'button';
   button.className = 'flashcard-button';
   button.setAttribute('aria-pressed', 'false');
   button.setAttribute('aria-label', `Karte ${cardData.de} umdrehen`);

   button.innerHTML = `
      <div class="flashcard-inner">
         <div class="flashcard-face flashcard-front">
            <div class="flashcard-label">Vorderseite</div>
            <div class="flashcard-word">${cardData.de}</div>
            <div class="flashcard-example">${cardData.deEx ?? ''}</div>
            <div class="flashcard-hint">Klicken zum Aufdecken</div>
         </div>
         <div class="flashcard-face flashcard-back">
            <div class="flashcard-label">${meta.label}</div>
            <div class="flashcard-word">${cardData[meta.key] ?? ''}</div>
            <div class="flashcard-example">${cardData[meta.exampleKey] ?? ''}</div>
            <div class="flashcard-hint">Klicken zum Zurückdrehen</div>
         </div>
      </div>
   `;

   button.addEventListener('click', () => {
      const isFlipped = article.classList.toggle('is-flipped');
      button.setAttribute('aria-pressed', String(isFlipped));

      if (isFlipped) {
         openCards.add(cardData.id);
      } else {
         openCards.delete(cardData.id);
      }

      updateProgress();
   });

   article.appendChild(button);
   return article;
}

Sprachpaket laden:

loadCards merkt sich die Auswahl, lädt die JSON-Datei mit der Fetch-API, mischt auf Wunsch und baut die Karten auf. Schlägt das Laden fehl, wird die Fehlermeldung angezeigt.


async function loadCards({ shuffle = true } = {}) {
   clearError();
   const selectedFile = languageSelect.value;
   localStorage.setItem(STORAGE_KEY, selectedFile);

   try {
      const response = await fetch(selectedFile, { cache: 'no-store' });

      if (!response.ok) {
         throw new Error(`Datei konnte nicht geladen werden: ${response.status}`);
      }

      const data = await response.json();
      currentCards = shuffle ? shuffleCards(data) : [...data];
      renderCards(currentCards);
   } catch (error) {
      currentCards = [];
      cardContainer.innerHTML = '';
      statusText.textContent = 'Fehler beim Laden';
      progressText.textContent = '0 / 0 aufgedeckt';
      showError('Das Sprachpaket konnte nicht geladen werden.');
      console.error(error);
   }
}

Alle Karten zurücksetzen:

Alle Karten werden wieder auf die Vorderseite gedreht, der Fortschritt beginnt bei null.


function resetCards() {
   document.querySelectorAll('.flashcard').forEach((card) => {
      card.classList.remove('is-flipped');
   });

   openCards = new Set();
   updateProgress();
}

Zusammen ergibt das eine kleine, gut lesbare Lernumgebung, in der sich Sprachpakete beliebig ergänzen lassen.

Quelle: Die erste Version entstand nach dem Tutorial 01 - Flash Card World - JSON +JavaScript + CSS Tutorial.