Fermer2
ZephLe 23/12/2017 à 15:28
Hello,

Comme vous le savez, l'image de Boo (la mascotte en haut à gauche des pages) change en fonction de la date et de la météo. Ce mécanisme fait pour l'instant partie du site, mais j'aimerais bien le remplacer pour permettre à ceux qui savent en dessiner d'en ajouter de nouvelles variantes sans avoir besoin de passer par moi. J'envisage de faire ça au moyen d'un site séparé, déployé indépendamment, dont le seul rôle est de définir l'image à afficher et l'éventuelle animation associée. Mon problème c'est que je n'arrive pas à faire ça sans dégrader le rendu du site à cause de clignotements ou de pages plus lentes à charger.

Le fonctionnement aujourd'hui

Actuellement c'est yAronet qui, à chaque affichage de page, peut déterminer quelle variante de Boo afficher et donc insérer directement la bonne URL dans la page. Un JavaScript s'occupe de la partie animation, s'il y a une animation définie pour le Boo sélectionné (par exemple le Boo "arbre de noël" affiché en ce moment). Ce script est chargé de façon asynchrone sur la page, ce qui signifie que l'animation démarre potentiellement que quelques millisecondes après que la page est chargée ce qui ne pose aucun problème puisque les animations sont lissées et sans transitions brutales. Le JavaScript est statique (pour favoriser sa mise en cache), c'est à dire qu'il ne dépend pas du Boo sélectionné. Pour savoir quel Boo est actif, il compare seulement l'URL de l'image avec celles qu'il connait pour savoir si une animation doit être démarrée.

En gros, ça fonctionne comme ça :
Pseudo-code du fonctionnement actuel
Page affichée par yAronet :
<img id="boo" src="boo/noel.png" /> <script type="text/javascript" src="boo/animation.js" async></script>
Script d'animation :
var boo = document.getElementById('boo'); switch (baseNameWithoutExtension(boo.src)) { case 'noel': startNoelAnimation(); break; }
Ça fonctionne bien, avec quand même un petit défaut que j'aimerais résoudre et que j'expliquerai plus tard.

Ce que j'avais envisagé de changer

Comme je le disais plus haut, je pensais déplacer ce fonctionnement dans un site dédié, qui serait responsable de choisir quel Boo afficher et s'occuper de servir à la fois l'image et le code JavaScript. yAronet se contenterait de référencer deux URLs vers ce nouveau service mais ne choisirait plus quel Boo doit être affiché. Voilà à quoi ça pourrait ressembler :
Pseudo-code du fonctionnement envisagé
Page affichée par yAronet :
<img id="boo" src="http://boo.yaronet.org/image.php" /> <script type="text/javascript" src="http://boo.yaronet.org/animation.js" async></script>
Script PHP de sélection d'image "image.php" :
<?php $boo = selectCurrentBooVariant(); header('Location: http://boo.yaronet.org/boo/' . $boo . '.png');
Le script d'animation resterait inchangé.
Mais ça ne peut pas fonctionner, car le script d'animation ne peut plus savoir quelle variante de Boo a été sélectionnée : la redirection d'URL effectuée dans le script PHP n'affecte pas le DOM de la page, donc quand le JavaScript s'exécute et lit la valeur de boo.src il obtient toujours http://boo.yaronet.org/image.php et non l'image réellement affichée. Il n'y a, à ma connaissance, aucun moyen de savoir qu'une redirection a eu lieu ni vers quelle URL. De même, impossible de lire les métadonnées de l'image une fois chargée pour savoir de laquelle il s'agit. Tout au plus je pourrais lire ses dimensions et essayer d'en déduire de laquelle il s'agit, mais ça serait très peu fiable (d'autant plus que la plupart des images actuelles font 75x75 pixels).

Le problème des deux requêtes

Le problème que je viens d'identifier peut se résumer facilement : je dois faire deux requêtes (une pour l'image et une pour le JavaScript) dont une seule va calculer quel Boo doit être affiché, mais l'autre doit pouvoir en tenir compte. Cette contraire vient du fait que ce calcul n'est pas gratuit (je fais appel à deux APIs dont le nombre de requêtes est limité pour connaître la météo) donc je ne peux pas le faire en double. D'autre part, même si je le faisais en double je risquerais d'avoir deux réponses différentes (par exemple lors d'un changement d'heure ou à la seconde précise où la météo change) et donc d'avoir des incohérences entre les résultats des deux requêtes.

En pratique il y a déjà une mise en cache de 15 minutes pour éviter d'appeler ces APIs à chaque page, mais je ne vois pas comment partager ce cache entre les deux requêtes. Si me débrouille par exemple pour que image.php écrive un cookie qui pourra être lu par le code JavaScript ça ne fonctionne pas, puisque le cookie se retrouverait sur le domaine boo.yaronet.org tandis que le script s'exécute sur www.yaronet.com. Des iframes ne sont pas envisageables puisque la taille de Boo n'est pas connue à l'avance.

Mes autres pistes clignotent

Bon, si je pars du principe qu'une seule de mes deux requêtes peut déterminer quel Boo à afficher et que la seconde doit se débrouiller pour le déduire sans refaire les calculs, ça limite pas mal mes options. Au lieu d'avoir une image dynamique et un script statique je peux faire l'inverse : générer un code JavaScript qui connait l'URL du Boo à afficher, et qui s'occupe de provoquer le chargement de la bonne image quand il s'exécute :
Pseudo-code de la solution avec JavaScript dynamique
Page affichée par yAronet :
<img id="boo" /> <script type="text/javascript" src="boo/animation.php" async></script>
Code PHP d'animation :
<?php // On calcule le Boo à afficher et on construit dynamiquement le code JavaScript adapté $boo = selectCurrentBooVariant(); $code = "var boo = document.getElementById('boo'); boo.src = 'http://boo.yaronet.org/boo/" . $boo . ".png'; switch (baseNameWithoutExtension(boo.src)) /* On pourrait même évaluer ce switch dès maintenant, mais ça ne simplifierait pas le problème */ { case 'noel': startNoelAnimation(); break; }"; // On envoie le tout comme s'il s'agissait d'un JavaScript header('Content-Length: ' . strlen($code)); header('Content-Type: text/javascript'); echo $code;
Cette fois ça fonctionne, mais le résultat est assez moche car le script s'exécute de façon asynchrone, donc la page est chargée entièrement avant que Boo n'apparaisse : ça clignote. On pourrait enlever l'attribut "async" du script et tant pis pour le temps de chargement de la page, mais ça ne change pas grand chose car même si le script tourne de façon synchrone la modification qu'il provoque sur le DOM ne l'est pas, donc le clignotement persiste.

En plus je n'ai pas tellement envie de faire ça, parce que déterminer quel Boo afficher prend quand même le temps de faire les appels à deux APIs (c'est à dire de l'ordre de quelques centaines de millisecondes). Ça n'arrive qu'une seule fois toutes les 15 minutes et le reste du temps tout est en cache donc l'affichage est quasi-instantané, mais j'aimerais quand même bien ne pas impacter le chargement de la page avec ça. Au passage, c'est le seul petit défaut de la solution actuelle que j'essaie de corriger au passage.

Bloqué !

Je pense que j'ai résumé mes tentatives actuelles, et pour le moment je bloque un peu. Est-ce que vous voyez une autre piste ? C'est rageant d'être bloqué pour un détail qui semble aussi simple smile