◆ Portail Antalaha

Annuaire › Code ouvert

Logo 3D animé pour un site d'association

tutorial · Voir le site ↗
Connectez-vous pour copier ce site

Code publié par son auteur, en lecture seule. « Copier dans mon espace » crée un nouveau site à vous (non publié) que vous pouvez modifier librement.

js/main.js · 7.1 Ko

/* ==========================================================================
 * js/main.js — LE CHEF D'ORCHESTRE
 * --------------------------------------------------------------------------
 * Ce fichier :
 *   1. crée l'Illustration Zdog (la « scène » liée au <canvas>) ;
 *   2. y ajoute le logo (fabriqué par js/logo.js) ;
 *   3. lance la boucle d'animation (requestAnimationFrame) ;
 *   4. branche les contrôles de la page (case à cocher, curseur, boutons).
 *
 * Ordre de chargement des scripts (voir index.html) :
 *   zdog.min.js -> config.js -> logo.js -> main.js
 * ========================================================================== */

(function () {
  'use strict';

  var R = AppLogo.REGLAGES;   // raccourci vers les réglages

  // ---- 1. ÉTAT DE L'ANIMATION ---------------------------------------------
  // Ces variables décrivent « ce qui se passe » à un instant donné.
  var etat = {
    auto: true,                // le balancement automatique est-il actif ?
    vitesse: 1,                // multiplicateur de vitesse (curseur de la page)
    phase: 0,                  // angle interne du balancement (sinus)
    glisse: false,             // l'utilisateur est-il en train de glisser ?
    tempsDepuisGlissement: 99, // secondes écoulées depuis la fin du glissement
    dernierTemps: 0,           // date (ms) de l'image précédente
  };

  // Respect des préférences d'accessibilité : si l'utilisateur a demandé
  // « moins d'animations » dans son système, on démarre sans mouvement auto.
  try {
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
      etat.auto = false;
    }
  } catch (e) { /* ancien navigateur : on garde la valeur par défaut */ }

  // ---- 2. CRÉATION DE LA SCÈNE --------------------------------------------
  // Illustration = la scène de plus haut niveau. Elle gère le <canvas>,
  // le dessin, et la rotation au glissement.
  var illo = new Zdog.Illustration({
    element: '#logo',     // sélecteur CSS du <canvas> (voir index.html)
    dragRotate: true,     // glisser (souris OU doigt) fait tourner la scène
    resize: true,         // le canevas suit la taille de son conteneur CSS.
                          // Zdog multiplie alors width/height par
                          // window.devicePixelRatio : le rendu reste NET sur
                          // les écrans haute densité (téléphones, Retina).

    // Fonction appelée au début d'un glissement : on suspend l'animation auto.
    onDragStart: function () {
      etat.glisse = true;
    },
    // Fonction appelée à la fin du glissement : on note l'heure de reprise.
    onDragEnd: function () {
      etat.glisse = false;
      etat.tempsDepuisGlissement = 0;
    },
    // Appelée à chaque changement de taille du canevas. `this` = l'illustration.
    onResize: function (largeur, hauteur) {
      this.zoom = calculerZoom(largeur, hauteur);
    },
  });

  /**
   * Calcule le zoom pour que le logo tienne toujours dans le canevas.
   * Le zoom multiplie TOUT (formes ET épaisseurs de trait).
   * @param {number} largeur Largeur CSS du canevas en pixels.
   * @param {number} hauteur Hauteur CSS du canevas en pixels.
   * @returns {number} Le facteur de zoom.
   */
  function calculerZoom(largeur, hauteur) {
    // On prend le plus petit côté, pour ne jamais déborder.
    return Math.min(largeur, hauteur) / R.hauteurScene;
  }

  // Zoom initial (on ne compte pas sur onResize pour le tout premier calcul).
  illo.zoom = calculerZoom(illo.width, illo.height);

  // Inclinaison de départ : un léger regard « du dessus ».
  illo.rotate.x = R.inclinaisonRepos;

  // ---- 3. AJOUT DU LOGO À LA SCÈNE ----------------------------------------
  var logo = AppLogo.creerLogo(illo, AppLogo.THEMES.ocean);

  // ---- 4. BOUCLE D'ANIMATION ----------------------------------------------
  /**
   * Appelée ~60 fois par seconde par le navigateur.
   * @param {number} maintenant Horodatage en millisecondes fourni par requestAnimationFrame.
   */
  function animer(maintenant) {
    // deltaTime : temps écoulé depuis l'image précédente, en SECONDES.
    // On le plafonne à 0,1 s : si l'onglet était caché, pas de saut brutal.
    var dt = Math.min(Math.max((maintenant - etat.dernierTemps) / 1000, 0), 0.1);
    etat.dernierTemps = maintenant;

    if (etat.auto && !etat.glisse) {
      etat.tempsDepuisGlissement += dt;

      // On attend un peu après un glissement avant de reprendre.
      if (etat.tempsDepuisGlissement > R.delaiReprise) {
        etat.phase += dt * R.vitesseBalancement * etat.vitesse;

        // Cible du balancement : un sinus oscille entre -1 et +1 doucement.
        var cibleY = Math.sin(etat.phase) * R.amplitudeBalancement;

        // On ne « saute » pas à la cible : on s'en approche progressivement
        // (interpolation). 1 - exp(-k·dt) donne un lissage indépendant du
        // nombre d'images par seconde.
        var k = 1 - Math.exp(-R.douceurRetour * dt);
        illo.rotate.y = Zdog.lerp(illo.rotate.y, cibleY, k);
        illo.rotate.x = Zdog.lerp(illo.rotate.x, R.inclinaisonRepos, k);
      }
    }

    // On redessine toute la scène : mise à jour puis rendu sur le canevas.
    illo.updateRenderGraph();

    // On demande au navigateur de rappeler animer() à la prochaine image.
    requestAnimationFrame(animer);
  }

  // Premier appel : on lance la boucle.
  requestAnimationFrame(animer);

  // ---- 5. CONTRÔLES DE LA PAGE --------------------------------------------
  // Petite fonction utilitaire : récupère un élément par son id (peut être null).
  function el(id) { return document.getElementById(id); }

  var caseAuto = el('ctrl-auto');
  var curseurVitesse = el('ctrl-vitesse');
  var texteVitesse = el('txt-vitesse');

  if (caseAuto) {
    caseAuto.checked = etat.auto;
    caseAuto.addEventListener('change', function () {
      etat.auto = caseAuto.checked;
    });
  }

  if (curseurVitesse) {
    curseurVitesse.addEventListener('input', function () {
      // .value est une CHAÎNE de caractères : on la convertit en nombre.
      etat.vitesse = parseFloat(curseurVitesse.value);
      if (texteVitesse) { texteVitesse.textContent = '×' + etat.vitesse.toFixed(1); }
    });
  }

  // Boutons de thème : chaque bouton porte data-theme="ocean" / "vanille" / …
  var boutons = document.querySelectorAll('[data-theme]');
  for (var i = 0; i < boutons.length; i++) {
    boutons[i].addEventListener('click', function (evenement) {
      var cle = evenement.currentTarget.getAttribute('data-theme');
      var theme = AppLogo.THEMES[cle];
      if (!theme) { return; }
      AppLogo.appliquerCouleurs(logo, theme);
      // Mise à jour visuelle des boutons (aria-pressed pour les lecteurs d'écran).
      for (var j = 0; j < boutons.length; j++) {
        boutons[j].setAttribute('aria-pressed', boutons[j] === evenement.currentTarget ? 'true' : 'false');
      }
    });
  }

  // Bouton « Remettre face à vous » : revient à la position de départ.
  var boutonRaz = el('btn-raz');
  if (boutonRaz) {
    boutonRaz.addEventListener('click', function () {
      illo.rotate.set({ x: R.inclinaisonRepos, y: 0, z: 0 });
      etat.tempsDepuisGlissement = 0;
    });
  }
})();