Ciao ![]()
L’idea di base
L’obiettivo era costruire un caricatore scheletrico (skeleton loader) che viene generato dall’interfaccia utente reale di Discourse, anziché affidarsi a un modello scheletrico predefinito in modo rigido (hardcoded).
Lo strumento di creazione (builder) consente a un amministratore di selezionare elementi reali sulla pagina e di trasformarli in regioni scheletriche.
Ad esempio:
.title
.avatar
.topic-excerpt
.btn
.category-breadcrumb
Il componente utilizza quindi questi selettori per generare lo scheletro in tempo di esecuzione.
Anteprima Scheletro
La parte interessante è che l’amministratore non deve scrivere i selettori manualmente. Lo strumento analizza l’elemento selezionato e genera diversi selettori candidati.
Generare selettori utili si è rivelato più difficile del previsto
Uno dei primi problemi a cui mi sono scontrato è stato la generazione dei selettori.
Un’implementazione ingenua può facilmente produrre qualcosa come:
.container.list-container.--topic-list .row.full-width .contents ...
Tecnicamente valido, ma troppo specifico per una configurazione scheletrica riutilizzabile.
Può diventare ancora peggio con le icone, dove il selettore generato potrebbe includere dettagli di implementazione come classi correlate a SVG.
Ciò che volevo davvero era qualcosa di più vicino a:
.badge-category__name
o:
.badge-category__wrapper .d-icon
anziché un selettore che descriva l’intero percorso DOM.
Quindi lo strumento ora genera più candidati e li valuta in base a fattori come:
- profondità del selettore
- numero di classi
- corrispondenze ripetute
- classi correlate allo stato
- classi tecniche SVG/icona
- se il selettore corrisponde ancora all’elemento selezionato
Il risultato è un elenco di selettori consigliati da cui l’amministratore può scegliere o che può modificare manualmente.
Elementi nascosti
C’è anche un selezionatore separato per gli elementi che dovrebbero semplicemente scomparire mentre lo scheletro è visualizzato.
Ad esempio:
.alert.alert-info
Si è rivelato utile per elementi come banner di annunci o notifiche temporanee che esistono durante la costruzione/test ma non dovrebbero influenzare il layout dello scheletro.
Un problema interessante qui era che nascondere un elemento non deve lasciare spazio vuoto dietro.
Quindi gli elementi esclusi non vengono trattati semplicemente come un elenco display: none - il calcolo della geometria deve anche capire che l’elemento non fa parte del layout finale.
Anteprima Scheletro
Navigazione
Probabilmente la sfida più grande è stata la navigazione.
Il comportamento desiderato era:
click
↓
mostra lo scheletro immediatamente
↓
Discourse cambia rotta
↓
appare il DOM di destinazione
↓
nascondi lo scheletro
La soluzione allettante era agganciarsi in profondità al ciclo di vita della navigazione e aspettare che il DOM si stabilizzasse completamente.
Si è rivelato l’approccio sbagliato.
In un certo momento lo scheletro poteva rimanere visibile per diversi secondi dopo che il contenuto reale era già presente.
La lezione è stata semplice:
Lo scheletro non dovrebbe diventare un gate di prontezza del DOM.
Una volta che la destinazione ha abbastanza contenuto reale da prendere il sopravvento, lo scheletro dovrebbe fare spazio.
Questo ha fatto una grande differenza nella velocità percepita della navigazione.
Viewport
Discourse ha già un sistema di viewport reattivo, quindi il componente ora utilizza la stessa astrazione di breakpoint:
xs
sm
md
lg
xl
2xl
La configurazione dello scheletro può essere ulteriormente raggruppata come:
mobile → xs / sm
tablet → md
desktop → lg / xl / 2xl
tutto → tutto
Questo significa che il componente non ha bisogno di conoscere i valori pixel effettivi in assoluto.
Se Discourse cambia i valori dei breakpoint, il componente dello scheletro non deve essere riscritto attorno a nuovi numeri predefiniti in modo rigido.
Cache della geometria
I selettori ci dicono cosa dovrebbe essere renderizzato, ma non ci dicono esattamente dove dovrebbero apparire le forme dello scheletro.
Per questo, ho aggiunto la cattura della geometria.
Lo strumento può misurare le regioni renderizzate reali e memorizzare la loro geometria in modo che il caricatore possa renderizzare immediatamente uno scheletro di destinazione durante la navigazione SPA.
C’è anche un’opzione esplicita di blocco della geometria per i casi in cui non voglio che le visite successive continuino a cambiare la geometria di riferimento.
Questa è stata un’altra distinzione importante:
definizione del selettore e geometria renderizzata sono due cose diverse.
Bozze
Un’altra cosa che è diventata necessaria è lo stato di bozza.
Non volevo questo flusso di lavoro:
apri lo strumento
→ trascorri 10 minuti a configurarlo
→ chiudi lo strumento
→ tutto è andato perduto
Lo strumento quindi mantiene una bozza in corso separatamente dall’impostazione del tema effettiva.
La bozza è limitata alla combinazione pagina/rotta/viewport, quindi ad esempio:
topic-list / lg
topic-list / md
topic-list / xs
non si sovrascrivono accidentalmente a vicenda.
Chiudere lo strumento non distrugge il lavoro svolto.
Annulla
Una volta che lo strumento è diventato più interattivo, un sistema di Annulla è diventato quasi inevitabile.
Lo strumento memorizza snapshot dello stato della sua configurazione:
{
"regions": \[\],
"excludes": \[\]
}
anziché cercare di mantenere una cronologia delle operazioni DOM.
Questo rende il sistema di annulla molto più facile da ragionare e lo mantiene anche indipendente dal DOM della pagina effettiva.
Questo progetto è in sviluppo attivo. Spero che sarà presto pronto per un componente di tema! ![]()




