L’API della cronologia HTML5 dà agli sviluppatori la possibilità di modificare l’URL di un sito web senza un aggiornamento completo della pagina. Questo è particolarmente utile per caricare porzioni di una pagina con JavaScript, così che il contenuto è significativamente diverso e giustifica un nuovo URL.
Ecco un esempio. Diciamo che una persona naviga dalla homepage di un sito alla pagina della Guida. Carichiamo il contenuto della pagina di aiuto con Ajax. Quell’utente poi si dirige verso la pagina Prodotti che carichiamo di nuovo e scambiamo il contenuto con Ajax. Poi vogliono condividere l’URL. Con l’API della cronologia, avremmo potuto cambiare l’URL della pagina insieme all’utente mentre naviga, così l’URL che vede (e quindi condivide o salva) è pertinente e corretto.
Le basi
Per verificare le caratteristiche di questa API è semplice come andare negli Strumenti di sviluppo e digitare history
nella console. Se l’API è supportata dal vostro browser preferito, troveremo una serie di metodi collegati a questo oggetto:
In questo tutorial siamo interessati ai metodi pushState
e replaceState
. Tornando alla console, possiamo sperimentare un po’ con i metodi e vedere cosa succede all’URL quando li usiamo. Tratteremo gli altri parametri di questa funzione più tardi, ma per ora tutto ciò di cui abbiamo bisogno è il parametro finale:
history.replaceState(null, null, 'hello');
Il metodo replaceState
qui sopra scambia l’URL nella barra degli indirizzi con ‘/hello’ nonostante nessuna risorsa sia richiesta e la finestra rimanga sulla stessa pagina. Eppure c’è un problema qui. Dopo aver premuto il pulsante indietro, scopriremo che non torneremo all’URL di questo articolo, ma torneremo alla pagina in cui ci trovavamo prima. Questo perché replaceState
non manipola la cronologia del browser, semplicemente sostituisce l’URL corrente nella barra degli indirizzi.
Per risolvere questo problema dovremo invece usare il metodo pushState
:
history.pushState(null, null, 'hello');
Ora se clicchiamo sul pulsante indietro dovremmo trovarlo che funziona come vorremmo, poiché pushState
ha cambiato la nostra cronologia per includere qualsiasi URL che abbiamo appena passato. Questo è interessante, ma cosa succede se proviamo qualcosa di un po’ subdolo e facciamo finta che l’URL corrente non sia affatto css-tricks.com, ma un altro sito web?
history.pushState(null, null, 'https://twitter.com/hello');
Questo lancerà un’eccezione perché l’URL deve essere della stessa origine di quello corrente, altrimenti potremmo rischiare gravi falle nella sicurezza e dare agli sviluppatori la possibilità di ingannare le persone facendogli credere di essere su un sito web completamente diverso.
Tornando agli altri parametri che vengono passati in questo metodo, possiamo riassumerli così:
history.pushState(, , );
- Il primo parametro è il dato che ci serve se lo stato della pagina web cambia, per esempio ogni volta che qualcuno preme il pulsante indietro o avanti nel suo browser. Notate che in Firefox questi dati sono limitati a 640k caratteri.
-
title
è il secondo parametro che può essere una stringa, ma al momento di scrivere, ogni browser semplicemente lo ignora. - Questo parametro finale è l’URL che vogliamo appaia nella barra degli indirizzi.
Una storia veloce
La cosa più significativa con queste API di storia è che non ricaricano la pagina. In passato, l’unico modo per cambiare l’URL era cambiare il window.location
che ricaricava sempre la pagina. Tranne se tutto ciò che si cambiava era il hash
(come quando si clicca un <a href="#target">link</a>
non si ricarica la pagina).
Questo ha portato al vecchio metodo hashbang di cambiare l’URL senza un aggiornamento completo della pagina. Famosamente, Twitter faceva le cose in questo modo ed è stato ampiamente criticato per questo (un hash non è una posizione “reale” della risorsa).
Twitter si è allontanato da questo, ed è stato uno dei primi sostenitori di questa API. Nel 2012 il team ha descritto il loro nuovo approccio. Qui delineano alcuni dei loro problemi quando lavorano a questo tipo di scala, mentre dettagliano anche come vari browser implementano questa specifica.
Un esempio usando pushState e Ajax
Costruiamo una demo!
Nella nostra interfaccia immaginaria vogliamo che gli utenti del nostro sito trovino informazioni su un personaggio di Ghostbusters. Quando selezionano un’immagine abbiamo bisogno che il testo su quel personaggio appaia sotto e vogliamo anche aggiungere una classe corrente ad ogni immagine in modo che sia chiaro chi è stato selezionato. Poi quando clicchiamo sul pulsante indietro la classe corrente passerà al personaggio precedentemente selezionato (e viceversa per il pulsante avanti) e naturalmente avremo bisogno che anche il contenuto sottostante torni indietro.
Ecco un esempio funzionante che possiamo sezionare:
Il markup per questo esempio è abbastanza semplice: abbiamo un .gallery
che contiene alcuni link e all’interno di ognuno di essi c’è un’immagine. Abbiamo poi il testo sottostante che vogliamo aggiornare con il nome selezionato e il div vuoto .content
che vogliamo sostituire con i dati dei rispettivi file HTML di ogni personaggio:
<div class="gallery"> <a href="/peter.html"> <img src="bill.png" alt="Peter" class="peter" data-name="peter"> </a> <a href="/ray.html"> <img src="ray.png" alt="Ray" class="ray" data-name="ray"> </a> <a href="/egon.html"> <img src="egon.png" alt="Egon" class="egon" data-name="egon"> </a> <a href="/winston.html"> <img src="winston.png" alt="Winston" class="winston" data-name="winston"> </a></div><p class="selected">Ghostbusters</p><p class="highlight"></p><div class="content"></div>
Senza alcun JavaScript questa pagina funzionerà ancora come dovrebbe, cliccando su un link si va alla pagina giusta e anche il pulsante indietro funziona proprio come un utente si aspetterebbe. Evviva l’accessibilità e il graceful degradation!
Prossimo passiamo a JavaScript dove possiamo iniziare ad aggiungere un gestore di eventi ad ogni link all’interno dell’elemento .gallery
usando la propagazione degli eventi, così:
var container = document.querySelector('.gallery');container.addEventListener('click', function(e) { if (e.target != e.currentTarget) { e.preventDefault(); // e.target is the image inside the link we just clicked. } e.stopPropagation();}, false);
All’interno di questa dichiarazione if
possiamo quindi assegnare l’attributo data-name
dell’immagine che selezioniamo alla variabile data
. Poi ci aggiungeremo “.html” e lo useremo come terzo parametro, l’URL che vorremmo caricare, nel nostro metodo pushState
(anche se in un esempio reale probabilmente vorremmo cambiare l’URL solo dopo che la richiesta Ajax ha avuto successo):
var data = e.target.getAttribute('data-name'), url = data + ".html"; history.pushState(null, null, url); // here we can fix the current classes // and update text with the data variable // and make an Ajax request for the .content element // finally we can manually update the document’s title
(In alternativa, potremmo anche prendere l’attributo href del link per questo.)
Ho sostituito il codice di lavoro con i commenti così possiamo concentrarci sul metodo pushState
per ora.
A questo punto, cliccando su un’immagine aggiorneremo la barra degli URL e il contenuto con la richiesta Ajax ma cliccando il pulsante indietro non ci manderà al carattere precedente che abbiamo selezionato. Quello che dobbiamo fare qui è fare un’altra richiesta Ajax quando l’utente clicca sul pulsante indietro/avanti e poi dovremo aggiornare l’URL ancora una volta con pushState
.
Prima torniamo indietro e aggiorniamo il parametro di stato del nostro metodo pushState
per nascondere questa informazione:
history.pushState(data, null, url);
Questo è il primo parametro, data
nel metodo sopra. Ora tutto ciò che è impostato su questa variabile sarà accessibile a noi in un evento popstate
che scatta ogni volta che l’utente clicca sui pulsanti avanti o indietro.
window.addEventListener('popstate', function(e) { // e.state is equal to the data-attribute of the last image we clicked});
Poi possiamo usare queste informazioni come vogliamo, che in questo caso è passare il nome del precedente Ghostbuster che abbiamo selezionato come parametro nella funzione Ajax requestContent
, che usa il metodo load
di jQuery:
function requestContent(file) { $('.content').load(file + ' .content');}window.addEventListener('popstate', function(e) { var character = e.state; if (character == null) { removeCurrentClass(); textWrapper.innerHTML = " "; content.innerHTML = " "; document.title = defaultTitle; } else { updateText(character); requestContent(character + ".html"); addCurrentClass(character); document.title = "Ghostbuster | " + character; }});
Se un utente dovesse cliccare sull’immagine di Ray il nostro listener di eventi si innescherebbe, il quale memorizzerebbe l’attributo data della nostra immagine all’interno dell’evento pushState
. Di conseguenza questo carica il file ray.html
che sarà richiamato se l’utente seleziona un’altra immagine e poi clicca il pulsante indietro.
Cosa ci rimane? Beh, se clicchiamo su un carattere e poi condividiamo l’URL che abbiamo aggiornato, allora quel file HTML verrebbe caricato al suo posto. Potrebbe essere un’esperienza meno confusa e preserveremo l’integrità dei nostri URL dando ai nostri utenti un’esperienza di navigazione più veloce in generale.
È importante riconoscere che l’esempio di cui sopra è semplicistico poiché caricare contenuti in questo modo con jQuery è molto disordinato e probabilmente vorremmo passare un oggetto più complesso nel nostro metodo pushState
ma ci mostra come possiamo iniziare immediatamente ad imparare come usare l’API History. Prima camminiamo, poi corriamo.
Se dovessimo usare questa tecnica su una scala più ampia allora dovremmo probabilmente considerare l’utilizzo di uno strumento progettato specificamente per questo scopo. Per esempio pjax è un plugin jQuery che accelera il processo di utilizzo simultaneo di Ajax e pushState, anche se supporta solo i browser che utilizzano l’API History.
History JS d’altra parte supporta i vecchi browser con il vecchio hash-fallback negli URL.
Cool URLs
Mi piace pensare agli URL, e in particolare faccio sempre riferimento a questo post sul design degli URL di Kyle Neath:
Gli URL sono universali. Funzionano in Firefox, Chrome, Safari, Internet Explorer, cURL, wget, il vostro iPhone, Android e persino scritte su note adesive. Sono l’unica sintassi universale del web. Non datelo per scontato. Qualsiasi utente semi-tecnico regolare del vostro sito dovrebbe essere in grado di navigare il 90% della vostra applicazione basandosi sulla memoria della struttura dell’URL. Per raggiungere questo obiettivo, i vostri URL dovranno essere pragmatici.
Questo significa che indipendentemente da qualsiasi hack o trucco per aumentare le prestazioni che potremmo voler implementare, gli sviluppatori web dovrebbero avere a cuore l’URL e con l’aiuto dell’API HTML5 History possiamo risolvere problemi come l’esempio precedente con solo un po’ di olio di gomito.
Gotchas comuni
- È spesso una buona idea incorporare la posizione di una richiesta Ajax negli attributi
href
di un elemento di ancoraggio. - Assicuratevi di
return true
dai gestori di click Javascript quando le persone fanno click centrale o a comando in modo da non sovrascriverli accidentalmente.
Ulteriori letture
- Documentazione di Mozilla sulla manipolazione della cronologia del browser
- L’esempio di galleria Ajax da Dive into HTML5
- Implementazione di Twitter di pushState
Supporto browser
Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |