Costruttore HTML personalizzato per schermate di avvio

:information_source: Sommario Plugin per Discourse che consente di personalizzare la schermata di caricamento (splash screen) utilizzando HTML e CSS definiti dall’amministratore.
:hammer_and_wrench: Link al Repository https://github.com/VaperinaDEV/custom-splash-html-builder
:heart: Ti è stato utile? > ./support --coffee
:open_book: Guida all’installazione Come installare i plugin in Discourse

Ciao :waving_hand:

Ho creato un piccolo plugin per Discourse che consente di personalizzare la schermata di caricamento (splash screen) utilizzando HTML e CSS definiti dall’amministratore, senza dover mantenere una copia modificata del template core di Discourse per la splash screen.

La motivazione originale alla base della creazione di questo plugin era in realtà le prestazioni su dispositivi mobili.

Volevo creare una schermata di caricamento animata più sofisticata, ma ho scoperto che le animazioni SVG supportate dall’implementazione core attuale di Discourse per la splash screen potevano diventare sorprendentemente problematiche sui dispositivi mobili.

Su desktop, l’animazione poteva apparire perfettamente fluida, mentre su mobile poteva diventare visibilmente a scatti, perdere fotogrammi, subire ritardi o persino sembrare bloccata durante l’animazione.

Dopo aver sperimentato con diversi approcci, ho scoperto che spostare l’animazione dall’SVG stesso a un elemento HTML circostante, come un <div>, ha fatto una differenza molto significativa.

Invece di animare continuamente i contenuti dell’SVG, l’SVG può rimanere statico mentre il browser anima lo strato HTML contenente utilizzando trasformazioni CSS.

Questo offre al browser una possibilità molto migliore di gestire l’animazione come un’operazione di composizione utilizzando l’hardware grafico del dispositivo.

Il risultato è stata un’animazione molto più fluida su mobile, senza i blocchi e i congelamenti che stavo riscontrando con l’approccio basato su SVG.

Questo è stato il motivo principale per cui è stato creato questo plugin.

Prima (SVG animato: l’animazione è lenta e si blocca)

Dopo (HTML animato: animazione fluida)


Il problema dell’animazione degli SVG

L’implementazione originale della splash screen è perfettamente adatta per un logo semplice o un’animazione relativamente leggera.

Tuttavia, una volta che l’animazione diventa più complessa, il rendering SVG può diventare costoso.

Ad esempio, un’animazione applicata direttamente a un SVG o ai suoi elementi interni può richiedere al browser di rielaborare o ridipingere ripetutamente parti dello SVG durante l’animazione.

Sui dispositivi mobili, questo può diventare particolarmente evidente.

Durante i test, ho riscontrato casi in cui l’animazione:

  • diventava visibilmente a scatti
  • si bloccava temporaneamente
  • sembrava fermarsi
  • si comportava significativamente peggio rispetto a desktop

La parte interessante era che la stessa animazione visiva poteva comportarsi in modo molto diverso a seconda di cosa veniva effettivamente animato.


Spostare l’animazione a uno strato HTML

L’approccio che ha funzionato molto meglio è stato quello di mantenere l’SVG stesso statico e inserirlo all’interno di un normale elemento HTML.

Ad esempio:

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

Invece di animare l’SVG, l’animazione viene applicata al contenitore:

#d-splash .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);
  }
}

Lo stesso SVG non cambia.

Il browser può quindi gestire la trasformazione dello strato HTML in modo molto più efficiente e, nei casi supportati, promuoverlo a uno strato composito gestito dall’hardware grafico.

Questo ha prodotto un risultato drammaticamente più fluido sui dispositivi mobili.

La distinzione importante è quindi la seguente:

Approccio core:

SVG
 └── Animazione SVG
      └── I contenuti SVG vengono animati

contro:

Approccio personalizzato:

Strato HTML
 └── SVG
      └── Trasformazione CSS sullo strato HTML
           └── Animazione compatibile con il compositor

Questo non è una garanzia che ogni animazione sarà accelerata dalla GPU. Il browser decide in ultima analisi come viene composta un’animazione, ma nei miei test la differenza è stata molto evidente.


Perché ho creato il Custom Splash HTML Builder

Una volta che questo approccio funzionava, avevo anche bisogno di un modo per costruire effettivamente la schermata di caricamento attorno ad esso.

Il template standard della splash screen non offre abbastanza flessibilità per questo tipo di implementazione.

Per un’animazione più complessa, potrei aver bisogno di:

  • più strati SVG
  • più contenitori HTML
  • elementi animati in modo indipendente
  • keyframes CSS personalizzati
  • tempistiche di animazione diverse
  • posizionamento personalizzato
  • markup completamente diverso dalla splash screen predefinita

Quindi, invece di creare un’altra implementazione di splash screen hard-coded, ho deciso di esporre la parte visiva attraverso due impostazioni di sito.

Il plugin aggiunge:

splash_custom_html

Il markup HTML/SVG renderizzato all’interno della schermata di caricamento.

splash_custom_css

Il CSS utilizzato dalla splash screen personalizzata, incluse animazioni, keyframes, posizionamento e comportamento reattivo.

Questo rende la splash screen effettivamente personalizzabile senza dover modificare il codice sorgente del plugin ogni volta che l’animazione cambia.


Editor admin integrato

Il plugin fornisce anche un piccolo editor admin integrato per gestire la splash screen personalizzata.

Aggiunge una sezione dedicata Splash HTML Builder nell’interfaccia di amministrazione di Discourse, con editor separati per:

  • HTML personalizzato
  • CSS personalizzato

Le modifiche possono essere salvate direttamente dall’interfaccia di amministrazione senza dover modificare manualmente le impostazioni di sito corrispondenti.

Le impostazioni sottostanti sono ancora:

  • splash_custom_html
  • splash_custom_css

L’editor è semplicemente un’interfaccia più comoda per gestirli.

Questo significa anche che il plugin non richiede la modifica dei file sorgente del plugin ogni volta che è necessario cambiare l’animazione della splash screen.


Esempio

Una splash screen personalizzata può contenere più strati indipendenti:

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

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

E ogni strato può avere la propria animazione:

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

#d-splash .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);
  }
}

Gli SVG rimangono statici mentre gli strati HTML circostanti vengono animati.

Questo rende possibile creare animazioni di splash screen considerevolmente più complesse mantenendo il lavoro di animazione costoso al di fuori dello stesso SVG.


Perché non sovrascrivere semplicemente il template core della splash screen?

Un altro obiettivo importante è stato quello di evitare di mantenere una copia del template core della splash screen di Discourse.

Un approccio diretto sarebbe quello di sovrascrivere:

app/views/common/_discourse_splash.html.erb

e copiare l’implementazione attuale di Discourse nel plugin.

Il problema è che questo crea un onere di manutenzione.

Se Discourse modifica la sua implementazione della splash screen in un rilascio futuro, il plugin conterrà ancora la vecchia versione.

Questo potrebbe potenzialmente portare a:

  • mancata inclusione di nuove modifiche core
  • mancata inclusione di miglioramenti delle prestazioni
  • comportamento interrotto dopo un aggiornamento di Discourse
  • dover confrontare manualmente il template del plugin con il core dopo ogni aggiornamento

Volevo evitare del tutto questa situazione.


Fallback al core

Il plugin supporta quindi una splash screen personalizzata con un fallback al core.

È configurato HTML personalizzato

Se:

SiteSetting.splash_custom_html.present?

allora il plugin renderizza la splash screen personalizzata.

L’HTML personalizzato è vuoto

Se non è stata configurata una splash screen personalizzata, il plugin fa riferimento al template core attuale della splash screen di Discourse.

Il plugin localizza il file core effettivo dall’installazione di Discourse in esecuzione:

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

e ne renderizza l’implementazione.

Concettualmente:

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

Questo significa che il plugin non porta una seconda copia del template core della splash screen.


Considerazioni sulle prestazioni

Il plugin non cerca di affermare che ogni animazione CSS diventerà magicamente accelerata dalla GPU.

Il browser decide ancora come vengono renderizzate e composte le singole animazioni.

L’obiettivo è invece quello di offrire al browser una struttura molto più favorevole per la composizione accelerata dall’hardware:

  • mantenere i contenuti SVG statici
  • isolare gli elementi animati in modo indipendente
  • animare gli strati HTML
  • preferire transform per movimento/scalatura/rotazione
  • evitare operazioni di ridipintura inutilmente costose
  • usare will-change dove appropriato

Ad esempio:

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

Questo approccio ha funzionato particolarmente bene per il mio caso d’uso ed ha eliminato gli scatti su mobile che stavo riscontrando con l’animazione SVG originale.


Abilitare o disabilitare la splash screen personalizzata

Il plugin fornisce anche un’impostazione di sito custom_splash_html_builder_enabled.

Quando è disabilitata, viene utilizzata la schermata di caricamento standard di Discourse indipendentemente dal fatto che siano stati configurati HTML o CSS personalizzati.

Questo fornisce un interruttore di sicurezza aggiuntivo per disabilitare temporaneamente la splash screen personalizzata senza eliminare l’HTML/CSS salvato.

La splash screen personalizzata viene renderizzata solo quando:

custom_splash_html_builder_enabled = true
splash_custom_html non è vuoto

Altrimenti, viene utilizzata la splash screen core attuale di Discourse.


Soprattutto, fornisce un modo per creare una splash screen animata personalizzata che funziona molto meglio su mobile animando gli strati HTML attorno a contenuti SVG statici invece di animare direttamente lo stesso SVG.

8 Mi Piace

Ciao :waving_hand:

Ho aggiunto data-color-scheme alla sezione #d-splash, così puoi impostare facilmente i loghi di splash per le modalità chiara e scura. DEV: Implement dynamic color scheme for splash section · VaperinaDEV/custom-splash-html-builder@ba6641b · GitHub

<%- splash_forced_scheme = (dark_color_scheme? || forced_dark_mode?) ? "dark" : (forced_light_mode? ? "light" : nil) %>

<section id="d-splash"<%= " data-color-scheme=\"#{splash_forced_scheme}\"".html_safe if splash_forced_scheme %>>

Così #d-splash riceve un attributo data-color-scheme="dark" / "light" quando viene imposta esplicitamente una modalità chiara o scura, e rimane senza attributo solo quando entrambe le modalità sono abilitate e si lascia che sia il sistema operativo a decidere.

La correzione consiste nel far sì che tale attributo abbia la priorità rispetto alla media query nel CSS personalizzato, ricorrendo a prefers-color-scheme solo quando l’attributo è assente.

Esempio:

HTML personalizzato

<!-- MODALITÀ CHIARA -->
<div class="custom-splash-light">
  <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>

<!-- MODALITÀ SCURA -->
<div class="custom-splash-dark">
  <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>

CSS personalizzato

#d-splash .custom-splash-dark {
  display: none;
}

/* Il sistema operativo decide — solo quando non c'è una modalità forzata */
@media (prefers-color-scheme: dark) {
  #d-splash:not([data-color-scheme]) .custom-splash-light {
    display: none;
  }
  #d-splash:not([data-color-scheme]) .custom-splash-dark {
    display: block;
  }
}

/* La modalità forzata vince sempre, indipendentemente dal sistema operativo */
#d-splash[data-color-scheme="light"] .custom-splash-dark {
  display: none;
}
#d-splash[data-color-scheme="dark"] .custom-splash-light {
  display: none;
}
#d-splash[data-color-scheme="dark"] .custom-splash-dark {
  display: block;
}

Poiché :not([data-color-scheme]) semplicemente non corrisponde una volta che l’attributo è presente, non c’è conflitto di specificità tra i due set di regole: la modalità forzata vince sempre.

2 Mi Piace

Fantastico, è qualcosa che suggerivo/cercavo anni fa, per controllare il branding/l’immagine quando la connessione è lenta, così che l’utente si orienti e si senta ancorato.

A prima vista è persino più di quanto avessi immaginato. Non vedo l’ora di provarlo in qualche momento. Ottimo lavoro!

2 Mi Piace