Come creare una barra di avanzamento lettura con JS: migliora UX e SEO
In questa guida spiego come indicare la progressione della lettura di una pagina web con una barra di avanzamento. Un utile esercizio di JavaScript, CSS e HTML.
Ultimamente pubblico articoli piuttosto lunghi. Preferisco creare approfondimenti, ma penso che i lettori abbiano bisogno di qualcosa renda la lettura più agevole. Ed ecco che ho deciso di creare una barra di avanzamento della lettura, una reading progress bar.
Ora, sarò onesto, le mie competenze in JavaScript sono... diciamo solo che non sono un drago. Ho dimestichezza con le basi, ma volevo un progetto che mi spingesse un po' più in là senza farmi impazzire nella programmazione. Una barra di avanzamento della lettura mi sembrava la "sfida" perfetta.
Il mio ragionamento era semplice: dare ai lettori un segnale visivo che indicasse a che punto fosse la lettura della pagina. Utile, immagino, soprattutto quando si ha a che fare con un cosiddetto wall of text, una pagina chilometrica. Inoltre, pensavo che questo tipo di miglioramento dell'esperienza utente potesse avere anche un impatto positivo sulla SEO: lettori felici, tempi di permanenza più lunghi: tutto si somma, no?
Quindi, mi sono rimboccato le maniche e ho iniziato a fare ricerca su Github. Si tratta di un componente banale, ho trovato alcuni pronti per React o Vue, scritti in TypeScript o altre diavolerie in cui non mi sono mai avventurato. Meglio il caro vanilla JavaScript. Volevo creare tutto da zero, per capire davvero come funzionava.
1 - Struttura HTML
Come sempre, si parte dal HTML corretto. Ciò significava incorporare l'elemento <progress>, che è il modo integrato di HTML5 per rappresentare qualsiasi progressione (sì), avanzamento. È semantico, cosa buona e giusta, quindi non serve creare un div generico. L'ho posizionato all'interno del <body>, prima del contenitore della pagina vera e propria e l'ho nominato con la classe "reading-progress".
<progress class="reading-progress" value="0" max="100"></progress>
<div class="page">
</div>
2 - Rendiamo il tutto bello con i CSS
Ecco, se con JavaScript metto le mani avanti affermando di non essere un drago, con i CSS penso di esserlo. Nel caso di questa progress bar, è necessario dargli delle dimensioni precise (se si va sul responsive è meglio) in modo che la larghezza copra tutto il layout della pagina, seguendo l'header e una altezza con un valore proporzionale in em, che ho già stabilito come custom property nel mio framework. Nel codice in basso, come esempio, ho messo 1em. Passiamo al posizionamento: position: fixed; mi assicura che la barra rimanga incollata in alto nella finestra di visualizzazione mentre l'utente scorre.
L'elemento <progress> fortunatamente prevede che vengano stabiliti colore color e colore dello sfondo. Anche in questo caso ho usato due custom properties già belle pronte (il colore primario e secondario del mio schema colori), anche se nell'esempio in basso i valori sono diversi.
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 1em; /* imposta a piacere */
appearance: none; /* questo rimuove gli stili default */
background-color: #eee; /* Background color */
color: #007bff; /* Progress bar color */
z-index: 100; /* per stare sicuri */
}
.reading-progress::-webkit-progress-value {
background-color: currentColor; /* usa il colore fissato in precedenza */
}
/* se si vuol essere pignoli, bisognerebbe inserire le regole con altri prefissi per gli innumerevoli browser esistenti al mondo */
3 - È ora di JavaScript
Quando c'è interazione tra gli elementi del DOM, è ora di chiamare in causa JS. Questa era la parte che mi preoccupava di più, ma si è rivelata semplice.
Ho iniziato selezionando la barra di avanzamento e il contenitore della pagina usando le rispettive classi HTML. Quando scrivo selezionare, voglio dire creare una variabile (una costante?) che vada a individuare ciascun elemento che sarà coinvolto nel JS. In seguito, ho aggiunto degli event listener per gli eventi scroll e resize. In questo modo, la barra di avanzamento si aggiorna senza problemi quando l'utente scorre e quando cambia la dimensione della finestra.
const progressBar = document.querySelector('.reading-progress'); // seleziona la barra
const pageContent = document.querySelector('.page'); // seleziona l'elemento che racchiude tutta la pagina
window.addEventListener('scroll', updateProgress); // "ascolta" gli eventi scroll dell'utente
window.addEventListener('resize', updateProgress); // "ascolta" un eventuale ridimensionamento della pagina
// adesso arriva il bello
Se te lo stessi chiedendo: perché la pagina dovrebbe ridimensionarsi? Un esempio banale può essere il caricamento lazy delle immagini all'interno dell'articolo, che ne modificherebbero l'altezza durante lo scrolling.
4 - Gestione della pagina in JS
Per la funzione che calcola lo scroll della pagina e attribuisce il valore alla progress bar, devo avere:
- l'altezza dell'elemento
.page - l'altezza della finestra attiva (viewport)
- la posizione corrente nella finestra attiva del browser
Quando ho visto gli script già creati, non mi era chiaro perché fosse necessario il punto 2. In effetti, è necessario avere un controllo nel caso il contenuto (punto 1) sia più corto della finestra. Ma non basta: l'altezza della finestra è indispensabile per calcolare di quanto scorra la pagina.
Ad esempio: la pagina è alta 3000px e la finestra 1000px. La "distanza" di scorrimento pagina sarà di 3000px - 1000px = 2000px. Se l'utente si fermasse a 1000px, sarebbe al 50% della pagina.
Qui sono affiorati i miei limiti nella programmazione, che spesso non mi permettono di astrarre alcuni meccanismi risolvibili matematicamente. Non mi vergogno nel dire che in questi casi Gemini AI mi è molto d'aiuto.
function updateProgress() {
const contentHeight = pageContent.scrollHeight; // altezza della pagina
const viewportHeight = window.innerHeight; // altezza della finestra
const scrollTop = window.scrollY; // posizione corrente scrolling
const progress = Math.min((scrollTop / (contentHeight - viewportHeight)) * 100, 100); // calcola l'avanzamento della progress bar
progressBar.value = progress; // aggiorna la barra in tempo reale
}
Un paio di appunti:
- ho cercato come ottenere l'altezza di un elemento HTML con JS e ho trovato la proprietà
Element.offsetHeight, tuttavia Gemini mi ha "consigliato" di usareElement.scrollHeightche è più appropriato, anche se nel mio caso ottengo lo stesso risultato. (scrollTop / (contentHeight - viewportHeight)) * 100trasforma in un valore percentuale;- il metodo
Math.min()è un controllo per avere un valore non sia maggiore di 100; - l'attributo
valuedell'elemento<progress>rende tutto più semplice.
5 - Aggiungo una condizione, che non guasta mai
Non sarò il miglior programmatore JavaScript sul mercato ma ho ben presenti alcuni principi, come l'eseguire del codice se un elemento è presente. La semplicissima funzione di questo script di certo non rallenterà Chrome ma è bene evitare di eseguire codice inutilmente. L'ho imparato qualche anno fa, nella scrittura frontend di un SaaS.
In questo modo, posso applicare una classe pre-determinata al body mentre scrivo il contenuto e attivare la progress bar.
// controlla se body ha la classe "long-form"
if (document.body.classList.contains('long-form')) {
// e qui va tutto il codice JS già trattato
}
Ed ecco completato questo piccolo accorgimento che può essere utile per la UX di chi legge i miei articoli e mi è stato utile per esercitarmi a creare un componente "tutto mio" senza copiare e incollare cose prese in giro per il Web.
Link e approfondimenti
- My Simple Implementation
- CSS Only Example (Viewport Hack) - usa un CSS hack che non ho apprezzato, efficace ma poco chiaro.
- More Advanced Component - un esperimento più complesso
- Element: scrollHeight property
- HTMLElement: offsetHeight property
- Math.min()
Dato che in questo articolo non ho inserito nemmeno un'immagine, ecco come mi sento dopo aver scritto 10 righe di codice:
