Générateur HTML personnalisé pour les écrans de démarrage

:information_source: Résumé Plugin Discourse permettant de personnaliser l’écran de chargement (splash screen) à l’aide de HTML et CSS définis par l’administrateur.
:hammer_and_wrench: Lien vers le dépôt https://github.com/VaperinaDEV/custom-splash-html-builder
:open_book: Guide d’installation Comment installer des plugins dans Discourse

Bonjour :waving_hand:

J’ai créé un petit plugin Discourse qui permet de personnaliser l’écran de chargement en utilisant du HTML et du CSS définis par l’administrateur, sans avoir à maintenir une copie modifiée du modèle de base de l’écran de chargement de Discourse.

La motivation initiale pour la création de ce plugin était en réalité les performances sur mobile.

Je voulais créer un écran de chargement animé plus sophistiqué, mais j’ai constaté que les animations SVG prises en charge par l’implémentation actuelle de l’écran de chargement de Discourse pouvaient poser des problèmes surprenants sur les appareils mobiles.

Sur ordinateur de bureau, l’animation pouvait paraître parfaitement fluide, tandis que sur mobile, elle pouvait devenir nettement saccadée, perdre des images, subir des latences, voire sembler s’arrêter en cours de route.

Après avoir expérimenté différentes approches, j’ai découvert que déplacer l’animation du SVG lui-même vers un élément HTML englobant, tel qu’un <div>, faisait une très grande différence.

Au lieu d’animer en continu le contenu du SVG, ce dernier peut rester statique tandis que le navigateur anime la couche HTML conteneur à l’aide de transformations CSS.

Cela donne au navigateur une bien meilleure opportunité de traiter l’animation comme une opération de composition en utilisant le matériel graphique de l’appareil.

Le résultat a été une animation beaucoup plus fluide sur mobile, sans les saccades et blocages que je constatais avec l’approche basée sur le SVG.

C’était la raison principale de la création de ce plugin.

Avant (SVG animé : l’animation subit des latences et s’arrête)

Après (HTML animé : animation fluide)


Le problème avec l’animation des SVG

L’implémentation originale de l’écran de chargement est tout à fait convenable pour un logo simple ou une animation relativement légère.

Cependant, dès que l’animation devient plus complexe, le rendu SVG peut devenir coûteux.

Par exemple, une animation appliquée directement à un SVG ou à ses éléments internes peut obliger le navigateur à traiter ou repeindre à plusieurs reprises des parties du SVG pendant l’animation.

Sur les appareils mobiles, cela peut devenir particulièrement visible.

Lors des tests, j’ai observé des cas où l’animation :

  • devenait visiblement saccadée
  • se figeait temporairement
  • semblait s’arrêter
  • se comportait nettement moins bien que sur ordinateur de bureau

L’aspect intéressant était que la même animation visuelle pouvait se comporter très différemment selon ce qui était réellement animé.


Déplacer l’animation vers une couche HTML

L’approche qui a fonctionné beaucoup mieux consistait à laisser le SVG lui-même statique et à le placer à l’intérieur d’un élément HTML normal.

Par exemple :

<div class="logo-layer">
  <svg viewBox="0 0 500 500">
    ...
  </svg>
</div>

Au lieu d’animer le SVG, l’animation est appliquée au conteneur :

.logo-layer {
  animation: pulse 1.8s ease-in-out infinite;
  will-change: transform;
}

@keyframes pulse {
  0%,
  100% {
    transform: scale(0.8);
  }

  50% {
    transform: scale(0.85);
  }
}

Le SVG lui-même ne change pas.

Le navigateur peut donc gérer la transformation de la couche HTML beaucoup plus efficacement, et dans les cas pris en charge, la promouvoir en une couche composite gérée par le matériel graphique.

Cela a produit un résultat nettement plus fluide sur les appareils mobiles.

La distinction importante est donc la suivante :

Approche de base :

SVG
 └── Animation SVG
      └── Le contenu du SVG est animé

contre :

Approche personnalisée :

Couche HTML
 └── SVG
      └── Transformation CSS sur la couche HTML
           └── Animation favorable à la composition

Cela ne garantit pas que chaque animation sera accélérée par le GPU. Le navigateur décide en última instance de la manière dont une animation est composée, mais lors de mes tests, la différence était très perceptible.


Pourquoi j’ai créé le Custom Splash HTML Builder

Une fois cette approche fonctionnelle, j’avais également besoin d’un moyen de construire réellement l’écran de chargement autour de celle-ci.

Le modèle d’écran de chargement standard ne fournit pas une flexibilité suffisante pour ce type d’implémentation.

Pour une animation plus complexe, j’aurais peut-être besoin de :

  • plusieurs couches SVG
  • plusieurs conteneurs HTML
  • des éléments animés de manière indépendante
  • des keyframes CSS personnalisés
  • des timings d’animation différents
  • un positionnement personnalisé
  • des couleurs adaptées au thème
  • une structure de balisage complètement différente de celle de l’écran de chargement par défaut

Plutôt que de créer une autre implémentation d’écran de chargement codée en dur, j’ai décidé d’exposer la partie visuelle via deux paramètres de site.

Le plugin ajoute :

splash_custom_html

La structure HTML/SVG rendue à l’intérieur de l’écran de chargement.

splash_custom_css

Le CSS utilisé par l’écran de chargement personnalisé, incluant les animations, les keyframes, le positionnement et le comportement responsive.

Cela rend l’écran de chargement effectivement personnalisable sans avoir à modifier le code source du plugin à chaque changement d’animation.


Éditeur administratif intégré

Le plugin fournit également un petit éditeur administratif intégré pour gérer l’écran de chargement personnalisé.

Il ajoute une section dédiée Splash HTML Builder dans l’interface d’administration de Discourse, avec des éditeurs séparés pour :

  • HTML personnalisé
  • CSS personnalisé

Les modifications peuvent être enregistrées directement depuis l’interface d’administration sans avoir à éditer manuellement les paramètres de site correspondants.

Les paramètres sous-jacents restent :

  • splash_custom_html
  • splash_custom_css

L’éditeur est simplement une interface plus pratique pour les gérer.

Cela signifie également que le plugin ne nécessite pas de modification des fichiers sources du plugin chaque fois que l’animation de l’écran de chargement doit être modifiée.


Exemple

Un écran de chargement personnalisé peut contenir plusieurs couches indépendantes :

<div class="splash-logo-container">

  <div class="ring-layer">
    <svg viewBox="0 0 500 500">
      ...
    </svg>
  </div>

  <div class="logo-layer">
    <svg viewBox="0 0 500 500">
      ...
    </svg>
  </div>

</div>

Et chaque couche peut avoir sa propre animation :

.ring-layer {
  animation: rotate 2.2s linear infinite;
  will-change: transform;
}

.logo-layer {
  animation: pulse 1.8s ease-in-out infinite;
  will-change: transform;
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

@keyframes pulse {
  0%,
  100% {
    transform: scale(0.8);
  }

  50% {
    transform: scale(0.85);
  }
}

Les SVG restent statiques tandis que les couches HTML englobantes sont animées.

Cela permet de créer des animations d’écran de chargement considérablement plus complexes tout en maintenant le travail d’animation coûteux en dehors du SVG lui-même.


Pourquoi ne pas simplement remplacer le modèle de base de l’écran de chargement ?

Un autre objectif important était d’éviter de maintenir une copie du modèle de base de l’écran de chargement de Discourse.

Une approche directe consisterait à remplacer :

app/views/common/_discourse_splash.html.erb
```\n
et à copier l’implémentation actuelle de Discourse dans le plugin.

Le problème est que cela crée une charge de maintenance.

Si Discourse modifie son implémentation de l’écran de chargement dans une future version, le plugin contiendrait toujours l’ancienne version.

Cela pourrait potentiellement entraîner :

* la non-inclusion des nouvelles modifications de la base
* la non-inclusion des améliorations de performances
* un comportement cassé après une mise à jour de Discourse
* la nécessité de comparer manuellement le modèle du plugin avec la base après chaque mise à jour

Je voulais éviter cela entièrement.

---

# Reprise par la base (fallback)

Le plugin prend donc en charge un écran de chargement personnalisé avec une reprise par la base.

### Le HTML personnalisé est configuré

Si :

SiteSetting.splash_custom_html.present?


alors le plugin rend l’écran de chargement personnalisé.

### Le HTML personnalisé est vide

Si aucun écran de chargement personnalisé n’a été configuré, le plugin utilise le **modèle de base de l’écran de chargement de Discourse actuel**.

Le plugin localise le fichier de base réel depuis l’installation Discourse en cours d’exécution :

Rails.root/app/views/common/_discourse_splash.html.erb


et rend cette implémentation.

Conceptuellement :

```ruby
core_splash_path = Rails.root.join("app", "views", "common", "_discourse_splash.html.erb")

if File.exist?(core_splash_path)
  render inline: File.read(core_splash_path), type: :erb
end

Cela signifie que le plugin ne contient pas de deuxième copie du modèle de base de l’écran de chargement.


Considérations de performance

Le plugin ne prétend pas que chaque animation CSS deviendra magiquement accélérée par le GPU.

Le navigateur décide toujours de la manière dont les animations individuelles sont rendues et composées.

L’objectif est plutôt de donner au navigateur une structure beaucoup plus favorable pour la composition accélérée par le matériel :

  • garder le contenu SVG statique
  • isoler les éléments animés de manière indépendante
  • animer les couches HTML
  • privilégier transform pour les mouvements/mises à l’échelle/rotations
  • éviter les opérations de repeinture inutilement coûteuses
  • utiliser will-change lorsque c’est approprié

Par exemple :

.ring-layer {
  will-change: transform;
  animation: rotate 2.2s linear infinite;
}

Cette approche a particulièrement bien fonctionné pour mon cas d’usage et a éliminé les saccades sur mobile que je constatais avec l’animation SVG originale.


Activer ou désactiver l’écran de chargement personnalisé

Le plugin fournit également un paramètre de site custom_splash_html_builder_enabled.

Lorsqu’il est désactivé, l’écran de chargement standard de Discourse est utilisé, indépendamment du fait qu’un HTML ou CSS personnalisé ait été configuré.

Cela fournit un interrupteur de sécurité supplémentaire pour désactiver temporairement l’écran de chargement personnalisé sans supprimer le HTML/CSS enregistré.

L’écran de chargement personnalisé n’est rendu que lorsque :

custom_splash_html_builder_enabled = true
splash_custom_html n’est pas vide

Sinon, l’écran de chargement de base de Discourse actuel est utilisé.


Plus important encore, il fournit un moyen de créer un écran de chargement animé personnalisé qui performe beaucoup mieux sur mobile en animant des couches HTML autour de contenu SVG statique au lieu d’animer directement le SVG lui-même.

1 « J'aime »