La documentation est en cours de rédaction, certaines informations sont manquantes
Tutoriel — HTML / CSS / JavaScript

Créer un jeu de
création d'avatar

Apprenez à construire un avatar personnalisable couche par couche, rendu en temps réel sur un Canvas HTML5.

Commencer le tutoriel

Introduction

Dans ce tutoriel, vous allez construire un créateur d'avatar interactif entièrement en HTML, CSS et JavaScript — sans aucune bibliothèque externe.

Le principe repose sur la superposition de couches d'images PNG : peau, cheveux, yeux, vêtements… chaque couche est dessinée dans l'ordre sur un canvas HTML5. L'utilisateur navigue entre les catégories, sélectionne une vignette ou une couleur, et l'aperçu se met à jour instantanément.

Prérequis

Avant de commencer, assurez-vous de disposer des éléments suivants :

Éditeur de code

Nous recommandons Visual Studio Code, mais tout éditeur de texte convient.

Notions HTML / CSS / JS

Vous devez maîtriser les bases du DOM (sélecteurs, événements), les propriétés CSS courantes et les fonctions JavaScript. Les notions d'async/await seront introduites progressivement.

Éléments de l'avatar

Préparez au moins 4 à 5 images par catégorie (coiffures, vêtements, yeux…). Format PNG obligatoire pour la transparence, taille identique pour toutes les couches — idéalement 1000×1000 px.

Un kit de démarrage est mis à disposition : il inclut la structure de dossiers prête à l'emploi, les fichiers JavaScript de base et un jeu d'images d'exemple pour démarrer sans attendre.

Télécharger le kit de démarrage

Architecture des dossiers

Voici l'arborescence complète du projet. Chaque catégorie d'avatar est un dossier dans sections/ — les catégories avec couleur ou genre imbriquent des sous-dossiers supplémentaires.

📦avatar-creator
 ┣ 📂sections
 ┃ ┣ 📂Eyes
 ┃ ┃ ┣ 📜1.png
 ┃ ┃ ┗ 📜(...).png
 ┃ ┣ 📂Hair
 ┃ ┃ ┣ 📂1
 ┃ ┃ ┃ ┣ 📜black.png
 ┃ ┃ ┃ ┗ 📜(...).png
 ┃ ┃ ┗ 📂(...)
 ┃ ┣ 📂Mouth
 ┃ ┣ 📂Nose
 ┃ ┣ 📂Skin
 ┃ ┃ ┣ 📂men
 ┃ ┃ ┗ 📂women
 ┃ ┗ 📂Top
 ┃   ┣ 📂men
 ┃   ┃ ┣ 📂1
 ┃   ┃ ┃ ┣ 📜black.png
 ┃   ┃ ┃ ┗ 📜(...).png
 ┃   ┃ ┗ 📂(...)
 ┃   ┗ 📂women
 ┣ 📂UI
 ┣ 📜global.js
 ┣ 📜index.html
 ┣ 📜reset.css
 ┗ 📜style.css

Bonnes pratiques

Utilisez des noms anglais pour les dossiers — ils seront repris directement comme valeurs data-element dans le HTML et comme clés dans avatarState.

Fichier reset.css

Un fichier reset.css neutralise les marges et styles par défaut du navigateur pour garantir un rendu identique sur tous les environnements.

Schéma d'une section avec couleur : [Nom]/[numéro]/[couleur.png] — par exemple Hair/3/blond.png. Pour les sections sans couleur, c'est simplement [Nom]/[numéro].png. Le dossier UI/ accueille les éléments graphiques d'interface.

HTML 01

La page est divisée en deux colonnes : à gauche le canvas d'aperçu en temps réel, à droite la navigation par catégorie et les vignettes de sélection.

HTML index.html Copier le code
<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Creation d'avatar</title>
    </head>
    <body>
    </body>
</html>

La colonne de gauche (#leftSection) n'affiche que le canvas. La colonne de droite (#rightSection) contient un <header> pour les boutons de navigation et un <main> pour les sections de vignettes.

HTML index.html Copier le code
<body>
    <div class="grid-2">
        <div id="leftSection">
            <div class="result">
                <canvas id="resultFinal" width="1000" height="1000"></canvas>
            </div>
        </div>
        <div id="rightSection">
            <header>
                <!-- Navigation des différentes sections -->
            </header>
            <main>
                <!-- Sections -->
            </main>
        </div>
    </div>
</body>
Pourquoi un canvas ? Le <canvas> permet de dessiner les couches dans un ordre précis et d'exporter le résultat final en PNG en un clic. C'est bien plus propre que d'empiler des <img> en position absolue.
La structure HTML est intentionnellement minimale à ce stade. Les sections suivantes viendront la compléter — chacune s'appuie sur ce qui a déjà été mis en place.

Navigation

Chaque bouton porte un attribut data-target dont la valeur correspond à l'id de la section à afficher. Le JavaScript lira cette valeur pour basculer entre les sections.

HTML index.html Copier le code
<header>
    <nav id="categories">
        <ul>
            <li><button type="button" data-target="Skin" class="active">Peau</button></li>
            <li><button type="button" data-target="Mouth">Bouche</button></li>
            <li><button type="button" data-target="Nose">Nez</button></li>
            <li><button type="button" data-target="Eyes">Regard</button></li>
            <li><button type="button" data-target="Hair">Cheveux</button></li>
            <li><button type="button" data-target="Top">Vetement</button></li>
        </ul>
    </nav>
</header>

Ajoutez un bouton de téléchargement à côté de la navigation. Il sera connecté au canvas en JavaScript pour déclencher l'export de l'avatar en PNG.

HTML index.html Copier le code
<button id="download">Mon Avatar est prêt !</button>

Sections d'avatar

Chaque section correspond à une catégorie d'avatar. Elle contient une grille de vignettes — chaque <li class="vignette"> porte un attribut data-element dont la valeur sera utilisée par JavaScript pour construire le chemin de l'image à charger sur le canvas.

Options partagées

Le genre sélectionné dans la section Peau (men ou women) est mémorisé dans la variable currentGender et répercuté automatiquement sur toutes les sections dépendantes, comme les vêtements.

Système de couleurs

Un clic sur une pastille met à jour la valeur color dans avatarState. JavaScript reconstruit alors le chemin de l'image — par exemple Hair/3/blond.png — et redessine le canvas.

Flexibilité : une section peut combiner genre et couleur — c'est le cas des vêtements. Le même schéma de dossiers et d'attributs data-* s'applique à toutes les catégories, il suffit de l'adapter.

Section sans options

Pour les catégories sans variante de couleur ni de genre (bouche, nez, yeux…), la structure est simple : une liste de vignettes numérotées, chacune pointant vers son image correspondante.

HTML index.html Copier le code
<section id="Mouth">
    <ul class="vignettes">
        <li class="vignette active" data-element="1"><img src="sections/Mouth/1.png" alt="Mouth - 1"></li>
        <li class="vignette" data-element="2"><img src="sections/Mouth/2.png" alt="Mouth - 2"></li>
        <li class="vignette" data-element="3"><img src="sections/Mouth/3.png" alt="Mouth - 3"></li>
        <li class="vignette" data-element="4"><img src="sections/Mouth/4.png" alt="Mouth - 4"></li>
        <li class="vignette" data-element="5"><img src="sections/Mouth/5.png" alt="Mouth - 5"></li>
        <li class="vignette" data-element="6"><img src="sections/Mouth/6.png" alt="Mouth - 6"></li>
        <li class="vignette" data-element="7"><img src="sections/Mouth/7.png" alt="Mouth - 7"></li>
        <li class="vignette" data-element="8"><img src="sections/Mouth/8.png" alt="Mouth - 8"></li>
    </ul>
</section>

Section avec genre (Peau)

La section Peau est particulière : le choix de vignette définit le genre (men ou women), et la palette de couleurs représente les carnations disponibles. La classe .active sur la <section> la rend visible au démarrage.

HTML index.html Copier le code
<section id="Skin" class="active">
    <ul class="vignettes">
        <li class="vignette active" data-element="men"><img src="sections/Skin/men/1.png" alt="Skin - men"></li>
        <li class="vignette" data-element="women"><img src="sections/Skin/women/1.png" alt="Skin - women"></li>
    </ul>
    <ul class="colors">
        <li class="color active" data-color="1" style="background-color: #eed8c5;"></li>
        <li class="color" data-color="2" style="background-color: #d1b39a;"></li>
        <li class="color" data-color="3" style="background-color: #bd9574;"></li>
        <li class="color" data-color="4" style="background-color: #ba875e;"></li>
        <li class="color" data-color="5" style="background-color: #9b6e4a;"></li>
        <li class="color" data-color="6" style="background-color: #775235;"></li>
        <li class="color" data-color="7" style="background-color: #68513f;"></li>
        <li class="color" data-color="8" style="background-color: #64554b;"></li>
    </ul>
</section>

Section avec palette de couleurs (Cheveux)

Chaque vignette affiche par défaut la variante black.png. La palette de couleurs permet de changer cette teinte dynamiquement — JavaScript reconstruit le chemin à chaque sélection. Ce même schéma s'applique à tout élément avec variantes de couleur.

HTML index.html Copier le code
<section id="Hair">
    <ul class="vignettes">
        <li class="vignette active" data-element="1"><img src="sections/Hair/1/black.png" alt="Hair - 1"></li>
        <li class="vignette" data-element="2"><img src="sections/Hair/2/black.png" alt="Hair - 2"></li>
        <li class="vignette" data-element="3"><img src="sections/Hair/3/black.png" alt="Hair - 3"></li>
        <li class="vignette" data-element="4"><img src="sections/Hair/4/black.png" alt="Hair - 4"></li>
        <li class="vignette" data-element="5"><img src="sections/Hair/5/black.png" alt="Hair - 5"></li>
        <li class="vignette" data-element="6"><img src="sections/Hair/6/black.png" alt="Hair - 6"></li>
        <li class="vignette" data-element="7"><img src="sections/Hair/7/black.png" alt="Hair - 7"></li>
        <li class="vignette" data-element="8"><img src="sections/Hair/8/black.png" alt="Hair - 8"></li>
        <li class="vignette" data-element="9"><img src="sections/Hair/9/black.png" alt="Hair - 9"></li>
        <li class="vignette" data-element="10"><img src="sections/Hair/10/black.png" alt="Hair - 10"></li>
    </ul>
    <ul class="colors">
        <li class="color active" data-color="black" style="background-color: #000000;"></li>
        <li class="color" data-color="darkbrown" style="background-color: #373018;"></li>
        <li class="color" data-color="brown" style="background-color: #7e623e;"></li>
        <li class="color" data-color="darkred" style="background-color: #a9612e;"></li>
        <li class="color" data-color="ginger" style="background-color: #db792e;"></li>
        <li class="color" data-color="blond" style="background-color: #dede7e;"></li>
        <li class="color" data-color="gray" style="background-color: #888888;"></li>
        <li class="color" data-color="white" style="background-color: #ededed;"></li>
    </ul>
</section>

Section avec genre + couleur (Vêtements)

Les vêtements combinent genre et couleur. JavaScript lit currentGender (défini dans la section Peau) et la couleur sélectionnée pour construire le chemin correct — par exemple sections/Top/men/1/red.png.

HTML index.html Copier le code
<section id="Top">
    <ul class="vignettes">
        <li class="vignette active" data-element="1"><img src="sections/Top/men/1/red.png" alt="Top - 1"></li>
        <li class="vignette" data-element="2"><img src="sections/Top/men/2/red.png" alt="Top - 2"></li>
        <li class="vignette" data-element="3"><img src="sections/Top/men/3/red.png" alt="Top - 3"></li>
        <li class="vignette" data-element="4"><img src="sections/Top/men/4/red.png" alt="Top - 4"></li>
        <li class="vignette" data-element="5"><img src="sections/Top/men/5/red.png" alt="Top - 5"></li>
        <li class="vignette" data-element="6"><img src="sections/Top/men/6/red.png" alt="Top - 6"></li>
    </ul>
    <ul class="colors">
        <li class="color" data-color="black" style="background-color: #000000;"></li>
        <li class="color" data-color="grey" style="background-color: #888888;"></li>
        <li class="color" data-color="white" style="background-color: #ffffff;"></li>
        <li class="color active" data-color="red" style="background-color: #ff0000;"></li>
        <li class="color" data-color="orange" style="background-color: #ff8800;"></li>
        <li class="color" data-color="yellow" style="background-color: #ffff00;"></li>
        <li class="color" data-color="green" style="background-color: #44ff44;"></li>
        <li class="color" data-color="blue" style="background-color: #0000ff;"></li>
        <li class="color" data-color="purple" style="background-color: #aa44aa;"></li>
    </ul>
</section>

La structure HTML est prête. Passons maintenant au CSS pour mettre en page l'interface et lui donner son apparence.

CSS 02

Créez style.css et liez-le dans le <head>. Ajoutez également un reset CSS en premier pour neutraliser les marges et styles par défaut des navigateurs.

HTML index.html Copier le code
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Creation d'avatar</title>
    <link rel="stylesheet" href="reset.css">
    <link rel="stylesheet" href="style.css">
</head>

Mise en page

CSS style.css Copier le code
/*---GLOBAL---*/
.grid-2 {
    display: grid;
    grid-template-columns: 40% 60%;
    grid-gap: 0px;
}
#leftSection {
    background-color: #eaeaea;
    padding: 16px;
    font-size: 16px;
}
#rightSection {
    background-color: #1f1f1f;
    color: white;
    padding: 16px;
    font-size: 16px;
}

Navigation

La navigation utilise flexbox pour aligner les boutons horizontalement. La classe .active indique la catégorie ouverte — elle sera ajoutée et retirée dynamiquement par JavaScript.

CSS style.css Copier le code
/*---NAVIGATION---*/
header {
    display: flex;
    justify-content: space-between;
    align-items: stretch;
    gap: 16px;
}
header nav ul {
    display: flex;
    gap: 16px;
    padding: 8px;
    background-color: white;
    border-radius: 8px;
    box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
}
button {
    padding: 8px;
    border-radius: 8px;
    border: none;
    background-color: #eaeaea;
    color: black;
    cursor: pointer;
    border: solid 2px #eaeaea;
    transition: all ease-in-out 0.5s;
}
button.active, button:hover {
    background-color: #1f1f1f;
    color: white;
}

Résultats

Le canvas occupe toute la largeur de son conteneur. En déclarant width="1000" height="1000" en HTML (qui fixe le ratio interne) et width: 100%; height: auto en CSS, le navigateur redimensionne l'élément en respectant son ratio carré — sans JavaScript.

CSS style.css Copier le code
/*---RESULT---*/
.result {
    overflow: hidden;
    max-width: 100%;
    background-color: white;
    border-radius: 1rem;
    margin: 1rem 2rem;
    box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 8px;
}

#resultFinal {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 1rem;
}

Sections & vignettes

Les vignettes sont disposées en grille responsive avec auto-fill — le nombre de colonnes s'adapte à la largeur disponible. Le pseudo-élément ::after avec padding-bottom: 100% est une astuce CSS classique pour forcer un ratio carré, quelle que soit la taille de la vignette.

CSS style.css Copier le code
/*---SECTIONS---*/
main section {
    display: none;
}
main section.active {
    padding: 16px 0px;
    display: block;
}

/*---VIGNETTES---*/
.vignettes {
    overflow-y: auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    grid-auto-rows: max-content;
    grid-gap: 16px;
}
.vignette {
    transition: all ease-in-out 0.5s;
    cursor: pointer;
    transition-duration: 300ms;
    box-shadow: rgba(0,0,0,0.18) 0px 2px 4px;
    border-radius: 16px;
    position: relative;
    background-color: white;
    overflow: hidden;
}
.vignette:after {
    content: "";
    display: block;
    padding-bottom: 100%;
}
.vignette img {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
    width: 100%;
    max-width: 100%;
    height: auto;
    image-rendering: optimizequality;
}

Couleurs

La palette est une grille de petites pastilles colorées. La classe .active, ajoutée par JavaScript au clic, signale visuellement la couleur en cours.

CSS style.css Copier le code
/*---COULEURS---*/
.colors {
    background-color: white;
    padding: 1rem;
    border-radius: 10px;
    box-shadow: rgba(0,0,0,0.18) 0px 2px 4px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
    grid-gap: 8px;
    height: max-content;
    margin-top: 1rem;
}
.color {
    display: block;
    width: 50px;
    height: 50px;
    border-radius: 10px;
    box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, .3) inset;
    transition: all ease-in-out 0.5s;
}
.color:active, .color:hover {
    box-shadow: 0px 0px 0px 2px rgb(255, 255, 255) inset;
    cursor: pointer;
}

JavaScript 03

Changer de catégorie

Au clic sur un bouton de navigation, on retire la classe .active de toutes les sections, puis on l'ajoute uniquement à la section dont l'id correspond au data-target du bouton cliqué.

JS global.js Copier le code
const navigationButtons = document.querySelectorAll('#categories button');
const sections = document.querySelectorAll('main section');

function switchSection() {
    navigationButtons.forEach(button => {
        button.addEventListener('click', function () {
            navigationButtons.forEach(b => b.classList.remove("active"));
            this.classList.add("active");
            const dataTarget = this.getAttribute('data-target');
            const sectionTarget = document.getElementById(dataTarget);
            sections.forEach(section => section.classList.remove("active"));
            sectionTarget.classList.add("active");
        });
    });
}

switchSection();

État de l'avatar

avatarState est la source de vérité unique de l'avatar : il mémorise l'élément et la couleur actifs pour chaque section. La fonction getLayerSrc traduit cet état en chemin de fichier, en tenant compte du genre et des variantes de couleur.

JS global.js Copier le code
let currentGender = 'men';
const sectionsWithGender = ['Top'];
const sectionsWithColor  = ['Skin', 'Hair', 'Top'];
const layerOrder = ['Skin', 'Hair', 'Eyes', 'Mouth', 'Nose', 'Top'];

const avatarState = {
    Skin:  { element: 'men', color: '1'    },
    Hair:  { element: '1',   color: 'black' },
    Eyes:  { element: '1' },
    Mouth: { element: '1' },
    Nose:  { element: '1' },
    Top:   { element: '1',   color: 'red'  }
};

function getLayerSrc(name) {
    const s = avatarState[name];
    if (sectionsWithColor.includes(name)) {
        if (sectionsWithGender.includes(name))
            return `sections/${name}/${currentGender}/${s.element}/${s.color}.png`;
        return `sections/${name}/${s.element}/${s.color}.png`;
    }
    if (sectionsWithGender.includes(name))
        return `sections/${name}/${currentGender}/${s.element}.png`;
    return `sections/${name}/${s.element}.png`;
}

Rendu sur Canvas

renderCanvas charge toutes les couches en parallèle grâce à Promise.all — bien plus rapide que de les charger une par une. Une fois toutes les images disponibles, elles sont dessinées dans l'ordre défini par layerOrder sur un fond blanc. Cette fonction est appelée à chaque changement de sélection.

JS global.js Copier le code
async function renderCanvas() {
    const canvas = document.getElementById('resultFinal');
    const ctx = canvas.getContext('2d');

    const images = await Promise.all(
        layerOrder.map(name => new Promise(resolve => {
            const img = new Image();
            img.onload  = () => resolve(img);
            img.onerror = () => resolve(null);
            img.src = getLayerSrc(name);
        }))
    );

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    for (const img of images) {
        if (img) ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
    }
}

renderCanvas();

Gestion des vignettes

Au clic sur une vignette, on met à jour avatarState[sectionName].element avec la valeur de data-element, puis on rappelle renderCanvas(). La section Skin est un cas particulier : son choix de vignette définit le genre, donc currentGender est également mis à jour.

JS global.js Copier le code
function changeShape() {
    for (const section of sections) {
        const sectionName = section.getAttribute('id');
        for (const vignette of section.querySelectorAll('.vignette')) {
            vignette.addEventListener('click', function () {
                this.parentElement.querySelector('.active')?.classList.remove('active');
                this.classList.add("active");
                avatarState[sectionName].element = this.getAttribute('data-element');
                if (sectionName === 'Skin') currentGender = avatarState.Skin.element;
                renderCanvas();
            });
        }
    }
}
changeShape();

Gestion des couleurs

La gestion des couleurs suit le même schéma : on met à jour avatarState[sectionName].color avec la valeur data-color de la pastille cliquée, puis on redessine le canvas.

JS global.js Copier le code
function changeColor() {
    for (const section of sections) {
        const sectionName = section.getAttribute('id');
        for (const color of section.querySelectorAll('.color')) {
            color.addEventListener('click', function () {
                this.parentElement.querySelector('.active')?.classList.remove('active');
                this.classList.add("active");
                avatarState[sectionName].color = this.getAttribute('data-color');
                renderCanvas();
            });
        }
    }
}
changeColor();

Téléchargement

L'un des avantages du canvas : l'export natif. canvas.toDataURL('image/png') retourne l'image encodée en base64, qu'il suffit d'affecter au href d'un lien temporaire pour déclencher le téléchargement. Assurez-vous que la variable canvas est accessible dans la portée de ce code.

JS global.js Copier le code
document.getElementById('download').addEventListener('click', () => {
    const link = document.createElement('a');
    link.download = 'mon-avatar.png';
    link.href = canvas.toDataURL('image/png');
    link.click();
});

Randomisation Optionnel

La fonction randomize parcourt toutes les sections, tire au sort une vignette et (si applicable) une couleur, met à jour avatarState en conséquence, puis redessine le canvas. Il suffit d'un bouton avec id="randomize" pour la déclencher.

HTML index.html Copier le code
<button id="randomize">Aléatoire</button>
JS global.js Copier le code
function randomize() {
    const pick = arr => arr[Math.floor(Math.random() * arr.length)];
    for (const section of sections) {
        const sectionName = section.getAttribute('id');
        const vignettes = [...section.querySelectorAll('.vignette')];
        if (!vignettes.length) continue;

        const v = pick(vignettes);
        section.querySelector('.vignette.active')?.classList.remove('active');
        v.classList.add('active');
        avatarState[sectionName].element = v.getAttribute('data-element');
        if (sectionName === 'Skin') currentGender = avatarState.Skin.element;

        const colors = [...section.querySelectorAll('.color')];
        if (colors.length) {
            const c = pick(colors);
            section.querySelector('.color.active')?.classList.remove('active');
            c.classList.add('active');
            avatarState[sectionName].color = c.getAttribute('data-color');
        }
    }
    renderCanvas();
}

document.getElementById('randomize').addEventListener('click', randomize);

Automatisation PHP Optionnel

Quand le projet grossit, maintenir manuellement la liste des <li> pour chaque image devient fastidieux et source d'erreurs. En renommant index.html en index.php, on peut déléguer cette tâche au serveur : PHP comptera les images dans chaque dossier via glob() et générera les vignettes automatiquement.

Prérequis : votre serveur doit supporter PHP (WAMP, XAMPP, MAMP ou hébergement PHP). Renommez simplement index.htmlindex.php.

Compter les images avec glob()

glob() retourne un tableau de tous les fichiers ou dossiers correspondant à un motif. Placez ces comptages en tout début de fichier PHP, avant toute sortie HTML — les variables seront disponibles dans tout le reste du template.

PHP index.php Copier le code
<?php
$mouthCount = count(glob('sections/Mouth/*.png'));
$noseCount  = count(glob('sections/Nose/*.png'));
$eyesCount  = count(glob('sections/Eyes/*.png'));
$hairCount  = count(glob('sections/Hair/*/', GLOB_ONLYDIR));
?>

Générer les vignettes en boucle

On remplace la liste statique de <li> par une boucle PHP. Désormais, ajouter une image dans le dossier sections/Mouth/ suffit — le HTML se régénère automatiquement sans aucune modification du code.

PHP index.php Copier le code
<section id="Mouth">
    <ul class="vignettes">
        <?php for ($i = 1; $i <= $mouthCount; $i++): ?>
            <li class="vignette <?= $i === 1 ? 'active' : '' ?>" data-element="<?= $i ?>">
                <img src="sections/Mouth/<?= $i ?>.png" alt="Mouth - <?= $i ?>">
            </li>
        <?php endfor; ?>
    </ul>
</section>
Dossiers imbriqués (Hair, Top…) : Pour les catégories organisées en sous-dossiers, utilisez le flag GLOB_ONLYDIRglob('sections/Hair/*/', GLOB_ONLYDIR) retourne uniquement les dossiers numériques (1/, 2/…). Le nom de chaque dossier devient la valeur de data-element.
PHP index.php Copier le code
<section id="Hair">
    <ul class="vignettes">
        <?php for ($i = 1; $i <= $hairCount; $i++): ?>
            <li class="vignette <?= $i === 1 ? 'active' : '' ?>" data-element="<?= $i ?>">
                <img src="sections/Hair/<?= $i ?>/black.png" alt="Hair - <?= $i ?>">
            </li>
        <?php endfor; ?>
    </ul>
    <!-- Palette de couleurs -->
</section>

Passer les comptes à JavaScript

Si votre JavaScript a besoin de connaître le nombre d'éléments par section (pour la randomisation, par exemple), PHP peut écrire ces valeurs directement dans un bloc <script> au moment de la génération de la page.

PHP index.php Copier le code
<script>
const sectionCounts = {
    Mouth: <?= $mouthCount ?>,
    Nose:  <?= $noseCount  ?>,
    Eyes:  <?= $eyesCount  ?>,
    Hair:  <?= $hairCount  ?>
};
</script>