0 XP
?
JavaScript : de zéro à la maîtrise/Projet final : Jeu, Galerie et Météo
Intermédiaire120 min80 XP

Objectifs de cette leçon

  • Construire un jeu de devinettes interactif
  • Créer une galerie d'images dynamique
  • Consommer une API météo

Projet final JavaScript 🏆

Ce projet final met en pratique toutes les competences acquises au cours du parcours JavaScript. Tu vas creer 3 mini-projets complets.


Mini-Projet 1 : Application de gestion de taches (Todo App) ✅

Cahier des charges

  • Ajouter, supprimer, marquer comme termine
  • Filtrer (tous, en cours, termines)
  • Sauvegarde dans localStorage
  • Compteur de taches restantes

ETAPE 1 : Le HTML

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Todo App</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
        .termine { text-decoration: line-through; color: #999; }
        .filtres button { margin: 0 5px; }
        .filtres button.actif { font-weight: bold; }
        .tache { display: flex; align-items: center; gap: 10px; padding: 8px 0; }
        .tache .supprimer { color: red; cursor: pointer; }
    </style>
</head>
<body>
    <h1>Mes Taches</h1>
    <form id="formulaire">
        <input type="text" id="input" placeholder="Ajouter une tache..." required>
        <button type="submit">Ajouter</button>
    </form>
    <div class="filtres">
        <button class="actif" data-filtre="tous">Tous</button>
        <button data-filtre="en-cours">En cours</button>
        <button data-filtre="termines">Termines</button>
    </div>
    <ul id="liste"></ul>
    <p id="compteur"></p>
    <script src="app.js"></script>
</body>
</html>

ETAPE 2 : Le JavaScript — Modele de donnees

// Charger les taches depuis localStorage
function chargerTaches() {
    const donnees = localStorage.getItem("taches");
    return donnees ? JSON.parse(donnees) : [];
}

// Sauvegarder dans localStorage
function sauvegarderTaches(taches) {
    localStorage.setItem("taches", JSON.stringify(taches));
}

// Generer un ID unique
function genererId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
}

ETAPE 3 : Les operations CRUD

let taches = chargerTaches();
let filtreActuel = "tous";

function ajouterTache(texte) {
    const nouvelleTache = {
        id: genererId(),
        texte,
        terminee: false,
        dateCreation: new Date().toISOString()
    };
    taches.push(nouvelleTache);
    sauvegarderTaches(taches);
    afficherTaches();
}

function supprimerTache(id) {
    taches = taches.filter(t => t.id !== id);
    sauvegarderTaches(taches);
    afficherTaches();
}

function basculerTache(id) {
    const tache = taches.find(t => t.id === id);
    if (tache) {
        tache.terminee = !tache.terminee;
        sauvegarderTaches(taches);
        afficherTaches();
    }
}

ETAPE 4 : Le rendu

function filtrerTaches() {
    switch (filtreActuel) {
        case "en-cours":
            return taches.filter(t => !t.terminee);
        case "termines":
            return taches.filter(t => t.terminee);
        default:
            return taches;
    }
}

function afficherTaches() {
    const liste = document.getElementById("liste");
    const tachesFiltrees = filtrerTaches();

    liste.innerHTML = tachesFiltrees.map(tache => `
        <li class="tache ${tache.terminee ? "termine" : ""}">
            <input
                type="checkbox"
                ${tache.terminee ? "checked" : ""}
                onchange="basculerTache('${tache.id}')"
            >
            <span>${tache.texte}</span>
            <span class="supprimer" onclick="supprimerTache('${tache.id}')">✕</span>
        </li>
    `).join("");

    // Mettre a jour le compteur
    const restantes = taches.filter(t => !t.terminee).length;
    document.getElementById("compteur").textContent =
        `${restantes} tache${restantes > 1 ? "s" : ""} restante${restantes > 1 ? "s" : ""}`;
}

ETAPE 5 : Les evenements

// Formulaire
document.getElementById("formulaire").addEventListener("submit", (e) => {
    e.preventDefault();
    const input = document.getElementById("input");
    const texte = input.value.trim();
    if (texte) {
        ajouterTache(texte);
        input.value = "";
    }
});

// Filtres — delegation d'evenements
document.querySelector(".filtres").addEventListener("click", (e) => {
    if (e.target.tagName === "BUTTON") {
        filtreActuel = e.target.dataset.filtre;
        document.querySelectorAll(".filtres button").forEach(btn => {
            btn.classList.remove("actif");
        });
        e.target.classList.add("actif");
        afficherTaches();
    }
});

// Afficher au chargement
afficherTaches();

Mini-Projet 2 : Météo avec API 🌤️

Cahier des charges

  • Rechercher une ville
  • Afficher temperature, humidite, vent
  • Historique des recherches
  • Gestion d'erreurs

Le code complet

const API_KEY = "Ta cle API OpenWeatherMap";

class MeteoApp {
    constructor() {
        this.historique = JSON.parse(localStorage.getItem("historiqueMeteo")) || [];
        this.init();
    }

    init() {
        document.getElementById("formulaire").addEventListener("submit", (e) => {
            e.preventDefault();
            const ville = document.getElementById("ville").value.trim();
            if (ville) this.rechercher(ville);
        });

        this.afficherHistorique();
    }

    async rechercher(ville) {
        try {
            this.afficherChargement();

            const reponse = await fetch(
                `https://api.openweathermap.org/data/2.5/weather?q=${ville}&appid=${API_KEY}&units=metric&lang=fr`
            );

            if (!reponse.ok) {
                throw new Error("Ville non trouvee");
            }

            const donnees = await reponse.json();
            this.afficherMeteo(donnees);
            this.ajouterHistorique(donnees);

        } catch (erreur) {
            this.afficherErreur(erreur.message);
        }
    }

    afficherMeteo(donnees) {
        const { name, main, weather, wind } = donnees;
        document.getElementById("resultat").innerHTML = `
            <div class="meteo-card">
                <h2>${name}</h2>
                <div class="temperature">${Math.round(main.temp)}°C</div>
                <div class="description">${weather[0].description}</div>
                <div class="details">
                    <span>Humidite : ${main.humidity}%</span>
                    <span>Vent : ${wind.speed} km/h</span>
                    <span>Ressenti : ${Math.round(main.feels_like)}°C</span>
                </div>
            </div>
        `;
    }

    afficherErreur(message) {
        document.getElementById("resultat").innerHTML =
            `<div class="erreur">❌ ${message}</div>`;
    }

    afficherChargement() {
        document.getElementById("resultat").innerHTML =
            `<div class="chargement">Chargement...</div>`;
    }

    ajouterHistorique(donnees) {
        this.historique.unshift({
            ville: donnees.name,
            temperature: donnees.main.temp,
            date: new Date().toLocaleString("fr-FR")
        });
        this.historique = this.historique.slice(0, 10);
        localStorage.setItem("historiqueMeteo", JSON.stringify(this.historique));
        this.afficherHistorique();
    }

    afficherHistorique() {
        const liste = document.getElementById("historique");
        if (!liste) return;
        liste.innerHTML = this.historique.map(h => `
            <li>${h.ville} : ${Math.round(h.temperature)}°C (${h.date})</li>
        `).join("");
    }
}

// Lancer l'application
new MeteoApp();

Mini-Projet 3 : Quiz interactif 🧠

Cahier des charges

  • 10 questions a choix multiples
  • Score en temps reel
  • Barre de progression
  • Classement local

Le code complet

const questions = [
    {
        question: "Quel langage est le plus utilise pour le web frontend ?",
        options: ["Python", "JavaScript", "Java", "C++"],
        reponse: 1
    },
    {
        question: "Quelle est la methode pour selectionner un element par son ID ?",
        options: ["querySelector()", "getElement()", "getElementById()", "selectById()"],
        reponse: 2
    },
    {
        question: "Que retourne typeof null ?",
        options: ["null", "undefined", "object", "boolean"],
        reponse: 2
    },
    {
        question: "Quel mot-cle declare une constante ?",
        options: ["var", "let", "const", "fixed"],
        reponse: 2
    },
    {
        question: "Quelle methode transforme un tableau avec une fonction ?",
        options: ["forEach()", "map()", "filter()", "reduce()"],
        reponse: 1
    },
    {
        question: "Que fait le spread operator (...) ?",
        options: ["Copie profonde", "Etale les elements", "Filtre les doublons", "Trie le tableau"],
        reponse: 1
    },
    {
        question: "Quel est l'equivalent moderne de XMLHttpRequest ?",
        options: ["fetch()", "ajax()", "request()", "http()"],
        reponse: 0
    },
    {
        question: "Comment declare-t-on une fonction async ?",
        options: ["function async", "async function", "await function", "function await"],
        reponse: 1
    },
    {
        question: "Que fait optional chaining (?.) ?",
        options: ["Cree un lien", "Accede en securite", "Modifie une propriete", "Supprime une propriete"],
        reponse: 1
    },
    {
        question: "Quelle est la difference entre == et === ?",
        options: [
            "Aucune",
            "=== verifie le type",
            "== est plus rapide",
            "=== est deprecie"
        ],
        reponse: 1
    }
];

class Quiz {
    constructor() {
        this.questionActuelle = 0;
        this.score = 0;
        this.repondu = false;
        this.classement = JSON.parse(localStorage.getItem("classementQuiz")) || [];
    }

    demarrer() {
        this.afficherQuestion();
    }

    afficherQuestion() {
        const q = questions[this.questionActuelle];
        const progression = ((this.questionActuelle) / questions.length) * 100;

        document.getElementById("quiz").innerHTML = `
            <div class="progression">
                <div class="barre" style="width: ${progression}%"></div>
            </div>
            <div class="question-numero">
                Question ${this.questionActuelle + 1}/${questions.length}
            </div>
            <h2>${q.question}</h2>
            <div class="options">
                ${q.options.map((opt, i) => `
                    <button class="option" onclick="quiz.repondre(${i})">
                        ${opt}
                    </button>
                `).join("")}
            </div>
            <div class="score-en-cours">Score : ${this.score}/${questions.length}</div>
        `;
    }

    repondre(index) {
        if (this.repondu) return;
        this.repondu = true;

        const q = questions[this.questionActuelle];
        const options = document.querySelectorAll(".option");

        options.forEach((opt, i) => {
            if (i === q.reponse) {
                opt.classList.add("correct");
            } else if (i === index) {
                opt.classList.add("incorrect");
            }
        });

        if (index === q.reponse) {
            this.score++;
        }

        setTimeout(() => {
            this.repondu = false;
            this.questionActuelle++;
            if (this.questionActuelle < questions.length) {
                this.afficherQuestion();
            } else {
                this.afficherResultat();
            }
        }, 1500);
    }

    afficherResultat() {
        const pourcentage = Math.round((this.score / questions.length) * 100);
        this.classement.push({
            score: this.score,
            total: questions.length,
            pourcentage,
            date: new Date().toLocaleString("fr-FR")
        });
        this.classement.sort((a, b) => b.pourcentage - a.pourcentage);
        this.classement = this.classement.slice(0, 5);
        localStorage.setItem("classementQuiz", JSON.stringify(this.classement));

        document.getElementById("quiz").innerHTML = `
            <div class="resultat">
                <h2>Quiz termine !</h2>
                <div class="score-final">${this.score}/${questions.length}</div>
                <div class="pourcentage">${pourcentage}%</div>
                <h3>Classement :</h3>
                <ul>
                    ${this.classement.map((e, i) => `
                        <li>${i + 1}. ${e.score}/${e.total} (${e.pourcentage}%)</li>
                    `).join("")}
                </ul>
                <button onclick="quiz = new Quiz(); quiz.demarrer();">
                    Recommencer
                </button>
            </div>
        `;
    }
}

let quiz = new Quiz();
quiz.demarrer();

Bilan du parcours JavaScript 🎓

CompetenceNiveau
Variables et types
Fonctions et closures
Tableaux et methodes
Objets et destructuration
DOM et evenements
Promesses et async/await
Modules ES
ES6+ avance
Projets concrets

🎉 Felicitations ! Tu as termine le parcours JavaScript complet. Tu maitrises maintenant les bases solides pour developper des applications web modernes. Continue a pratiquer, a creer des projets, et a explorer les frameworks comme React, Vue ou Angular !

⚡ Simulateur — Projet : Jeu de devinettes

Étape 1/10
📝 Pseudo-code
1function jouer() {
2 const secret = Math.floor(Math.random() * 100) + 1;
3 let tentative = 0;
4 while (tentative !== secret) {
5 tentative++;
6 }
7 return tentative;
8}
⚙️ Exécution
📦 Mémoire
Aucune variable
🔀 Flux d'exécution
function jouer()
3
Définition du jeu

function jouer() définit la fonction du jeu de devinettes. Elle contient toute la logique : génération aléatoire, boucle et retour du résultat.