Annuaire › Code ouvert
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 · 11.2 Ko
/* ==========================================================================
* js/main.js — POINT D'ENTRÉE : illustration, interface et boucle d'animation
* --------------------------------------------------------------------------
* Ce fichier est chargé en DERNIER. Il :
* 1. crée l'Illustration Zdog (canevas net, zoom adaptatif, rotation au glissement) ;
* 2. construit la scène (Baie.creerScene, voir scene.js) ;
* 3. branche l'interface : heure, pause, temps qui défile, rotation lente ;
* 4. lance la boucle d'animation avec un deltaTime.
*
* LA BOUCLE D'ANIMATION EN RÉSUMÉ
* Le navigateur appelle animer() avant chaque affichage (~60 fois/s). On
* mesure le temps écoulé depuis la dernière image : c'est le « deltaTime »
* (dt). Tout mouvement est calculé ainsi : position += vitesse × dt
* Résultat : la pirogue avance à la MÊME vitesse sur un écran à 30 images/s
* comme sur un écran à 120 images/s.
*
* PERFORMANCES : dans animer(), on ne crée AUCUNE forme Zdog et presque aucun
* objet. Les palettes de couleurs (chaînes CSS) ne sont recalculées qu'environ
* 12 fois par seconde, et seulement si l'heure a changé.
* ========================================================================== */
(function () {
'use strict';
var CFG = Baie.CONFIG;
// ---- 1. ÉTAT DE L'APPLICATION -------------------------------------------
var etat = {
heure: CFG.heureDepart, // heure de la journée affichée (0 à 24)
tempsAuto: true, // le temps s'écoule-t-il tout seul ?
pause: false, // animation en pause ?
rotationLente: false, // la vue tourne-t-elle doucement ?
glisse: false, // l'utilisateur est-il en train de glisser sur le canevas ?
temps: 0, // temps d'animation cumulé (s) ; s'arrête en pause
dernier: 0, // horodatage de l'image précédente (ms)
chronoPalette: 0, // temps réel écoulé depuis le dernier recalcul de palette
heurePalette: -1, // heure pour laquelle la palette a été calculée
fps: 60, // images par seconde, lissées
chronoFps: 0, // pour n'afficher les FPS que 2 fois par seconde
};
// Respect des préférences d'accessibilité : « réduire les animations ».
try {
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
etat.tempsAuto = false;
}
} catch (e) { /* on garde les valeurs par défaut */ }
// ---- 2. L'ILLUSTRATION ---------------------------------------------------
/**
* Zoom adaptatif : toute la scène (largeurScene × hauteurScene unités)
* doit tenir dans le canevas, qu'il soit large (ordinateur) ou étroit
* (téléphone). On prend le plus petit des deux rapports.
* @param {number} largeur Largeur CSS du canevas (pixels).
* @param {number} hauteur Hauteur CSS du canevas (pixels).
* @returns {number} Le zoom à appliquer.
*/
function calculerZoom(largeur, hauteur) {
return Math.min(largeur / CFG.largeurScene, hauteur / CFG.hauteurScene);
}
var illo = new Zdog.Illustration({
element: '#baie-canvas',
dragRotate: true, // le doigt / la souris fait tourner le diorama
resize: true, // le canevas suit sa boîte CSS. Zdog multiplie sa résolution
// par window.devicePixelRatio : rendu net sur écrans denses.
rotate: { x: CFG.vueDepart.x, y: CFG.vueDepart.y },
onDragStart: function () { etat.glisse = true; },
onDragEnd: function () { etat.glisse = false; },
// Appelée à chaque redimensionnement (rotation du téléphone, fenêtre…).
// `this` est l'illustration.
onResize: function (largeur, hauteur) { this.zoom = calculerZoom(largeur, hauteur); },
});
illo.zoom = calculerZoom(illo.width, illo.height);
// ---- 3. LA SCÈNE ---------------------------------------------------------
var scene = Baie.creerScene(illo);
// ---- 4. RÉFÉRENCES VERS L'INTERFACE --------------------------------------
function el(id) { return document.getElementById(id); }
var zoneScene = el('scene-baie'); // la boîte du canevas (porte le dégradé de ciel)
var curseurHeure = el('ctrl-heure');
var texteHeure = el('txt-heure');
var textePhase = el('txt-phase');
var iconeAstre = el('ico-astre');
var caseTemps = el('chk-temps');
var caseRotation = el('chk-rotation');
var boutonPause = el('btn-pause');
var iconePause = el('ico-pause');
var libellePause = el('lib-pause');
var ruban = el('ruban');
var repere = el('ruban-repere');
var texteFps = el('info-fps');
// ---- 5. LA PALETTE -------------------------------------------------------
// Objet créé UNE FOIS et réutilisé à chaque calcul (voir palette.js).
var palette = Baie.Palette.creerSortie();
/**
* Formate une heure décimale en texte lisible : 14.5 -> « 14 h 30 ».
* @param {number} h Heure décimale.
* @returns {string}
*/
function formaterHeure(h) {
var heures = Math.floor(h);
var minutes = Math.floor((h - heures) * 60);
return heures + ' h ' + (minutes < 10 ? '0' : '') + minutes;
}
/**
* Recalcule la palette pour l'heure courante et l'applique PARTOUT :
* formes 3D, fond CSS du ciel, textes de l'interface.
* Appelée environ 12 fois par seconde au maximum (voir animer()).
*/
function majPalette() {
Baie.Palette.calculer(etat.heure, palette);
scene.appliquerPalette(palette);
etat.heurePalette = etat.heure;
etat.chronoPalette = 0;
// Le dégradé du ciel : du CSS appliqué derrière le canevas transparent.
if (zoneScene) {
zoneScene.style.background =
'linear-gradient(180deg, ' + palette.css.cielHaut + ' 0%, ' + palette.css.cielBas + ' 100%)';
}
// Textes et repères de l'interface.
var texte = formaterHeure(etat.heure);
if (texteHeure) { texteHeure.textContent = texte; }
if (textePhase) { textePhase.textContent = palette.phase; }
if (curseurHeure) {
curseurHeure.value = etat.heure.toFixed(2);
curseurHeure.setAttribute('aria-valuetext', texte + ', ' + palette.phase);
}
if (repere) { repere.style.left = (etat.heure / 24 * 100).toFixed(2) + '%'; }
if (iconeAstre) {
var nuit = palette.etoiles > 0.5;
iconeAstre.src = nuit ? 'medias/lune.svg' : 'medias/soleil.svg';
}
}
/**
* Change l'heure à la main (curseur ou ruban) : on coupe le défilement
* automatique et on met l'affichage à jour tout de suite.
* @param {number} heure Nouvelle heure.
*/
function choisirHeure(heure) {
etat.heure = Zdog.modulo(heure, 24);
etat.tempsAuto = false;
if (caseTemps) { caseTemps.checked = false; }
majPalette();
}
// ---- 6. ÉVÉNEMENTS DE L'INTERFACE ----------------------------------------
if (curseurHeure) {
curseurHeure.min = '0';
curseurHeure.max = '24';
curseurHeure.addEventListener('input', function () {
choisirHeure(parseFloat(curseurHeure.value)); // .value est du texte : on convertit
});
}
if (caseTemps) {
caseTemps.checked = etat.tempsAuto;
caseTemps.addEventListener('change', function () { etat.tempsAuto = caseTemps.checked; });
}
if (caseRotation) {
caseRotation.addEventListener('change', function () { etat.rotationLente = caseRotation.checked; });
}
if (boutonPause) {
boutonPause.addEventListener('click', function () {
etat.pause = !etat.pause;
boutonPause.setAttribute('aria-pressed', etat.pause ? 'true' : 'false');
if (libellePause) { libellePause.textContent = etat.pause ? 'Reprendre' : 'Pause'; }
if (iconePause) { iconePause.src = etat.pause ? 'medias/lecture.svg' : 'medias/pause.svg'; }
});
}
// Bouton « Vue de départ » : remet la caméra dans sa position initiale.
var boutonVue = el('btn-vue');
if (boutonVue) {
boutonVue.addEventListener('click', function () {
illo.rotate.set({ x: CFG.vueDepart.x, y: CFG.vueDepart.y, z: 0 });
});
}
// ---- 7. Zdog.Dragger : GLISSER SUR LE RUBAN DU TEMPS ---------------------
// Dragger est la classe de Zdog qui suit un glissement (souris ou doigt).
// `Illustration` en hérite (c'est ce qui permet dragRotate). Ici on l'utilise
// SEUL, sur un autre élément : le « ruban » coloré qui représente les 24 h.
/**
* Convertit la position horizontale d'un pointeur en heure (0 à 24).
* @param {{pageX:number}} pointeur Événement ou toucher fourni par Dragger.
* @returns {number} L'heure correspondante.
*/
function heureDepuisPointeur(pointeur) {
var boite = ruban.getBoundingClientRect();
var x = pointeur.pageX - (window.pageXOffset || 0) - boite.left; // position dans le ruban
return Baie.borner(x / (boite.width || 1), 0, 1) * 24;
}
if (ruban) {
new Zdog.Dragger({
startElement: ruban,
onDragStart: function (pointeur) { choisirHeure(heureDepuisPointeur(pointeur)); },
onDragMove: function (pointeur) { choisirHeure(heureDepuisPointeur(pointeur)); },
});
}
// ---- 8. LA BOUCLE D'ANIMATION --------------------------------------------
/**
* Appelée par le navigateur avant chaque affichage.
* @param {number} maintenant Horodatage en millisecondes (fourni par requestAnimationFrame).
*/
function animer(maintenant) {
// a) deltaTime en SECONDES, borné entre 0 et deltaMax (0,1 s) : si l'onglet
// est resté caché, on évite un énorme saut au retour.
var dtReel = Baie.borner((maintenant - etat.dernier) / 1000, 0, CFG.deltaMax);
etat.dernier = maintenant;
// b) En pause, le temps d'animation est figé (dt = 0) mais on continue de
// dessiner : on peut toujours tourner la vue avec le doigt.
var dt = etat.pause ? 0 : dtReel;
etat.temps += dt;
// c) Le temps de la journée avance : 24 h en `dureeJournee` secondes.
if (etat.tempsAuto) {
etat.heure = Zdog.modulo(etat.heure + (dt * 24) / CFG.dureeJournee, 24);
}
// d) Palette : recalculée au plus ~12 fois par seconde, et seulement
// si l'heure a changé (les chaînes de couleurs ne sont pas gratuites).
etat.chronoPalette += dtReel;
if (etat.heure !== etat.heurePalette && etat.chronoPalette > 0.08) { majPalette(); }
// e) Caméra : rotation lente optionnelle, et inclinaison toujours bornée.
if (etat.rotationLente && !etat.glisse) { illo.rotate.y += CFG.rotationLente * dtReel; }
illo.rotate.x = Baie.borner(illo.rotate.x, CFG.inclinaisonMin, CFG.inclinaisonMax);
// f) On fait avancer la scène, puis on la dessine.
scene.mettreAJour(etat.temps, dt, etat.heure);
illo.updateRenderGraph();
// g) Compteur d'images par seconde (moyenne glissante), affiché 2 fois/s.
if (dtReel > 0) { etat.fps = etat.fps * 0.92 + (1 / dtReel) * 0.08; }
etat.chronoFps += dtReel;
if (texteFps && etat.chronoFps > 0.5) {
texteFps.textContent = Math.round(etat.fps) + ' images/s';
etat.chronoFps = 0;
}
requestAnimationFrame(animer);
}
// ---- 9. DÉMARRAGE --------------------------------------------------------
majPalette(); // première palette AVANT la première image
requestAnimationFrame(animer);
// Pour les curieux : la scène est accessible depuis la console du navigateur,
// par exemple Baie.debug.illo.rotate.y = 1;
Baie.debug = { illo: illo, scene: scene, etat: etat };
})();