De HTML5 History API biedt ontwikkelaars de mogelijkheid om de URL van een website te wijzigen zonder de pagina volledig te vernieuwen. Dit is met name handig voor het laden van delen van een pagina met JavaScript, zodat de inhoud aanzienlijk verschilt en een nieuwe URL rechtvaardigt.
Hier volgt een voorbeeld. Laten we zeggen dat iemand van de homepage van een site naar de Help-pagina navigeert. Wij laden de inhoud van die Help-pagina met Ajax. Die gebruiker gaat dan naar de Producten pagina die we opnieuw laden en de inhoud wisselen met Ajax. Dan willen ze de URL delen. Met de History API hadden we de URL van de pagina samen met de gebruiker kunnen veranderen, zodat de URL die ze zien (en dus delen of opslaan) relevant en correct is.
De basis
Om de mogelijkheden van deze API te bekijken is het zo simpel als naar de Developer Tools gaan en history
in de console typen. Als de API wordt ondersteund door de browser van uw keuze, vinden we een groot aantal methoden die aan dit object zijn gekoppeld:
We zijn geïnteresseerd in de pushState
en replaceState
methoden in deze tutorial. Als we terugkeren naar de console, kunnen we een beetje experimenteren met de methodes en zien wat er gebeurt met de URL als we ze gebruiken. We zullen de andere parameters in deze functie later behandelen, maar voor nu hoeven we alleen de laatste parameter te gebruiken:
history.replaceState(null, null, 'hello');
De replaceState
methode hierboven verwisselt de URL in de adresbalk met ‘/hello’ ondanks dat er geen assets worden opgevraagd en het venster op dezelfde pagina blijft. Toch is er hier een probleem. Wanneer we op de terug-knop drukken, merken we dat we niet terugkeren naar de URL van dit artikel, maar in plaats daarvan teruggaan naar de pagina waar we eerder waren. Dit komt omdat replaceState
de geschiedenis van de browser niet manipuleert, maar gewoon de huidige URL in de adresbalk vervangt.
Om dit te verhelpen moeten we de pushState
methode gebruiken:
history.pushState(null, null, 'hello');
Als we nu op de terug-knop klikken, zou deze moeten werken zoals we zouden willen, omdat pushState
onze geschiedenis heeft veranderd om de URL op te nemen die we er zojuist in hebben gezet. Dit is interessant, maar wat gebeurt er als we iets slinks proberen en doen alsof de huidige URL helemaal niet css-tricks.com was, maar een andere website?
history.pushState(null, null, 'https://twitter.com/hello');
Dit zal een exception opleveren omdat de URL van dezelfde oorsprong moet zijn als de huidige, anders riskeren we grote veiligheidslekken en geven we ontwikkelaars de mogelijkheid om mensen voor de gek te houden door ze te laten geloven dat ze op een totaal andere website zijn.
Om terug te komen op de andere parameters die aan deze methode worden doorgegeven, kunnen we ze als volgt samenvatten:
history.pushState(, , );
- De eerste parameter is de data die we nodig hebben als de toestand van de webpagina verandert, bijvoorbeeld wanneer iemand op de terug- of vooruitknop in zijn browser drukt. Merk op dat deze gegevens in Firefox beperkt zijn tot 640k tekens.
-
title
is de tweede parameter die een string kan zijn, maar op het moment van schrijven negeert elke browser deze gewoon. - De laatste parameter is de URL die we in de adresbalk willen laten verschijnen.
Een korte geschiedenis
Het belangrijkste van deze geschiedenis-API’s is dat ze de pagina niet opnieuw laden. In het verleden was de enige manier om de URL te veranderen het veranderen van de window.location
die altijd de pagina herlaadde. Behalve als je alleen de hash
veranderde (zoals wanneer je op een <a href="#target">link</a>
klikt, de pagina niet wordt herladen).
Dit leidde tot de oude hashbang methode om de URL te veranderen zonder een volledige pagina refresh. Twitter deed dat ook en kreeg daar veel kritiek op (een hash is geen “echte” bronlocatie).
Twitter stapte daarvan af, en was een van de vroege voorstanders van deze API. In 2012 beschreef het team hun nieuwe aanpak. Hier schetsen ze enkele van hun problemen bij het werken op deze schaal, terwijl ze ook in detail beschrijven hoe verschillende browsers deze specificatie implementeren.
Een voorbeeld met pushState en Ajax
Laten we een demo bouwen!
In onze denkbeeldige interface willen we dat de gebruikers van onze website informatie vinden over een personage uit Ghostbusters. Als ze een afbeelding selecteren, moet de tekst over dat personage eronder verschijnen en we willen ook een huidige klasse toevoegen aan elke afbeelding, zodat het duidelijk is wie er is geselecteerd. Als we dan op de terug-knop klikken, springt de huidige klasse naar het eerder geselecteerde personage (en vice-versa voor de vooruit-knop) en natuurlijk moet de inhoud eronder ook weer terugschakelen.
Hier volgt een werkend voorbeeld dat we kunnen ontleden:
De opmaak voor dit voorbeeld is eenvoudig genoeg: we hebben een .gallery
die een aantal links bevat en binnen elk daarvan een afbeelding. We hebben de tekst eronder die we willen updaten met de geselecteerde naam en de lege .content
div die we willen vervangen door de gegevens van de respectievelijke HTML-bestanden van elk karakter:
<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>
Zonder JavaScript functioneert deze pagina nog steeds zoals het hoort, klikken op een link leidt naar de juiste pagina en op de terug-knop klikken werkt ook precies zoals een gebruiker het zou verwachten. Joepie voor toegankelijkheid en graceful degradation!
Volgende stappen we over naar JavaScript waar we een event handler kunnen toevoegen aan elke link in het .gallery
element door gebruik te maken van event propagation, zoals dit:
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);
Binnen dit if
statement kunnen we dan het data-name
attribuut van de afbeelding die we selecteren toewijzen aan de data
variabele. Dan voegen we er “.html” aan toe en gebruiken dat als derde parameter, de URL die we willen laden, in onze pushState
methode (hoewel we in een echt voorbeeld waarschijnlijk de URL pas willen veranderen nadat het Ajax verzoek succesvol is geweest):
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
(Als alternatief kunnen we ook het href attribuut van de link hiervoor gebruiken.)
Ik heb de werkcode vervangen door commentaar zodat we ons nu kunnen richten op de pushState
methode.
Dus op dit punt zal klikken op een afbeelding de URL balk en de inhoud updaten met het Ajax verzoek maar klikken op de terug knop zal ons niet naar het vorige teken sturen dat we geselecteerd hebben. Wat we hier moeten doen is een ander Ajax verzoek doen als de gebruiker op de terug/vooruit knop klikt en dan moeten we de URL weer bijwerken met pushState
.
We gaan eerst terug en werken de state parameter van onze pushState
methode bij om die informatie op te slaan:
history.pushState(data, null, url);
Dit is de eerste parameter, data
in de methode hierboven. Alles wat nu in die variabele wordt gezet, is voor ons toegankelijk in een popstate
event dat afgaat als de gebruiker op de vooruit- of terugknop klikt.
window.addEventListener('popstate', function(e) { // e.state is equal to the data-attribute of the last image we clicked});
Daarna kunnen we deze informatie gebruiken zoals we willen, wat in dit geval het doorgeven van de naam van de vorige Ghostbuster die we hebben geselecteerd als parameter is in de Ajax requestContent
functie, die jQuery’s load
methode gebruikt:
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; }});
Als een gebruiker op het plaatje van Ray zou klikken, zou onze event listener afgaan, die dan het data attribuut van onze afbeelding zou opslaan binnen het pushState
event. Bijgevolg laadt dit het ray.html
bestand dat zal worden aangeroepen als de gebruiker een andere afbeelding selecteert en vervolgens op de terugknop klikt. *Phew*.
Wat blijft er nu over? Nou, als we op een teken klikken en dan de URL delen die we hebben bijgewerkt, dan zou dat HTML-bestand in plaats daarvan worden geladen. Het is misschien een minder verwarrende ervaring en we behouden de integriteit van onze URL’s terwijl we onze gebruikers over het geheel genomen een snellere browse-ervaring geven.
Het is belangrijk om te erkennen dat het bovenstaande voorbeeld simplistisch is omdat het laden van inhoud op deze manier met jQuery erg rommelig is en we waarschijnlijk een complexer object in onze pushState
methode zouden willen doorgeven, maar het laat ons zien hoe we direct kunnen beginnen met het leren gebruiken van de History API. Eerst lopen, dan rennen.
Als we deze techniek op grotere schaal zouden willen gebruiken, dan zouden we waarschijnlijk moeten overwegen om een tool te gebruiken die speciaal voor dat doel is ontworpen. Bijvoorbeeld pjax is een jQuery plugin die het proces van het gebruik van Ajax en pushState tegelijk versnelt, hoewel het alleen browsers ondersteunt die de History API gebruiken.
History JS daarentegen ondersteunt oudere browsers met de oude hash-fallback in de URL’s.
Coole URL’s
Ik denk graag na over URL’s, en ik refereer met name aan deze post over URL-ontwerp van Kyle Neath de hele tijd:
URL’s zijn universeel. Ze werken in Firefox, Chrome, Safari, Internet Explorer, cURL, wget, je iPhone, Android en zelfs opgeschreven op sticky notes. Ze zijn de enige universele syntaxis van het web. Neem dat niet voor lief. Elke gewone semi-technische gebruiker van uw site zou in staat moeten zijn om 90% van uw app te navigeren op basis van het geheugen van de URL-structuur. Om dit te bereiken, moeten je URL’s pragmatisch zijn.
Dit betekent dat webontwikkelaars, ongeacht eventuele hacks of prestatieverhogende trucs die we willen implementeren, de URL moeten koesteren en met behulp van de HTML5 History API kunnen we problemen zoals het bovenstaande voorbeeld met een beetje ellebogenwerk oplossen.
Veelvoorkomende problemen
- Het is vaak een goed idee om de locatie van een Ajax-verzoek in te sluiten in de
href
-attributen van een ankerelement. - Zorg ervoor dat
return true
van Javascript-klikhandlers wanneer mensen in het midden of op commando klikken, zodat we ze niet per ongeluk overschrijven.
Verder lezen
- Mozilla’s documentatie over het manipuleren van de browsergeschiedenis
- Het Ajax gallery voorbeeld uit Dive into HTML5
- Twitter’s implementatie van pushState
Browser ondersteuning
Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |