Die HTML5-History-API gibt Entwicklern die Möglichkeit, die URL einer Website zu ändern, ohne die Seite komplett zu aktualisieren. Dies ist besonders nützlich, wenn Teile einer Seite mit JavaScript geladen werden, so dass sich der Inhalt deutlich verändert und eine neue URL rechtfertigt.
Hier ist ein Beispiel. Nehmen wir an, eine Person navigiert von der Startseite einer Website zur Hilfeseite. Wir laden den Inhalt dieser Hilfeseite mit Ajax. Dieser Benutzer geht dann zur Seite „Produkte“, die wir wieder mit Ajax laden und den Inhalt auslagern. Dann wollen sie die URL weitergeben. Mit der Verlaufs-API hätten wir die URL der Seite direkt mit dem Benutzer ändern können, während er navigiert, so dass die URL, die er sieht (und somit teilt oder speichert), relevant und korrekt ist.
Die Grundlagen
Um die Funktionen dieser API zu testen, ist es so einfach, wie in die Entwicklerwerkzeuge zu gehen und history
in die Konsole einzugeben. Wenn die API im Browser Ihrer Wahl unterstützt wird, finden wir eine Reihe von Methoden, die mit diesem Objekt verbunden sind:
In diesem Tutorial sind wir an den Methoden pushState
und replaceState
interessiert. Wenn wir zur Konsole zurückkehren, können wir ein wenig mit den Methoden experimentieren und sehen, was mit der URL passiert, wenn wir sie verwenden. Wir werden die anderen Parameter in dieser Funktion später behandeln, aber im Moment brauchen wir nur den letzten Parameter:
history.replaceState(null, null, 'hello');
Die obige replaceState
-Methode tauscht die URL in der Adressleiste mit ‚/hello‘ aus, obwohl keine Assets angefordert werden und das Fenster auf derselben Seite bleibt. Dennoch gibt es hier ein Problem. Wenn wir auf den Zurück-Button klicken, werden wir feststellen, dass wir nicht zur URL dieses Artikels zurückkehren, sondern zu der Seite, auf der wir vorher waren. Das liegt daran, dass replaceState
nicht den Verlauf des Browsers manipuliert, sondern einfach die aktuelle URL in der Adressleiste ersetzt.
Um das zu beheben, müssen wir stattdessen die pushState
-Methode verwenden:
history.pushState(null, null, 'hello');
Wenn wir jetzt auf den Zurück-Button klicken, sollte er so funktionieren, wie wir es gerne hätten, da pushState
unseren Verlauf so verändert hat, dass er die URL enthält, die wir gerade eingegeben haben. Das ist interessant, aber was passiert, wenn wir ein wenig trickreich vorgehen und so tun, als wäre die aktuelle URL gar nicht css-tricks.com, sondern eine ganz andere Website?
history.pushState(null, null, 'https://twitter.com/hello');
Das wird eine Exception auslösen, weil die URL den gleichen Ursprung haben muss wie die aktuelle, sonst riskieren wir große Sicherheitslücken und geben den Entwicklern die Möglichkeit, den Leuten vorzugaukeln, sie seien auf einer ganz anderen Website.
Zurück zu den anderen Parametern, die in diese Methode übergeben werden, können wir sie wie folgt zusammenfassen:
history.pushState(, , );
- Der erste Parameter sind die Daten, die wir benötigen, wenn sich der Zustand der Webseite ändert, zum Beispiel wenn jemand die Zurück- oder Vorwärts-Taste in seinem Browser drückt. Beachten Sie, dass diese Daten in Firefox auf 640k Zeichen begrenzt sind.
-
title
ist der zweite Parameter, der ein String sein kann, aber zum Zeitpunkt des Schreibens ignoriert ihn jeder Browser einfach. - Dieser letzte Parameter ist die URL, die in der Adressleiste erscheinen soll.
Eine kurze Geschichte
Das Wichtigste bei diesen History-APIs ist, dass sie die Seite nicht neu laden. In der Vergangenheit bestand die einzige Möglichkeit, die URL zu ändern, darin, das window.location
zu ändern, wodurch die Seite immer neu geladen wurde. Es sei denn, man änderte nur das hash
(so wie ein Klick auf ein <a href="#target">link</a>
die Seite nicht neu lädt).
Das führte zu der alten Hashbang-Methode, um die URL zu ändern, ohne die Seite komplett zu aktualisieren. Bekanntlich hat Twitter das früher so gemacht und wurde dafür stark kritisiert (ein Hash ist kein „echter“ Ressourcenort).
Twitter ist davon abgerückt und war einer der frühen Befürworter dieser API. Im Jahr 2012 beschrieb das Team seinen neuen Ansatz. Hier skizzieren sie einige ihrer Probleme, wenn sie in dieser Größenordnung arbeiten, während sie auch detailliert beschreiben, wie verschiedene Browser diese Spezifikation implementieren.
Ein Beispiel mit pushState und Ajax
Lassen Sie uns eine Demo bauen!
In unserer imaginären Schnittstelle möchten wir, dass die Benutzer unserer Website Informationen über einen Charakter aus Ghostbusters finden. Wenn er ein Bild auswählt, soll der Text über die Figur darunter erscheinen, und wir wollen außerdem jedem Bild eine aktuelle Klasse hinzufügen, damit klar ist, wer ausgewählt wurde. Wenn man dann auf den Zurück-Button klickt, soll die aktuelle Klasse zum vorher ausgewählten Charakter springen (und umgekehrt für den Vorwärts-Button) und natürlich soll der Inhalt darunter auch wieder zurück wechseln.
Hier ist ein funktionierendes Beispiel, das wir sezieren können:
Das Markup für dieses Beispiel ist einfach genug: Wir haben ein .gallery
, das einige Links enthält, und in jedem dieser Links befindet sich ein Bild. Dann haben wir den Text darunter, den wir mit dem ausgewählten Namen aktualisieren wollen, und das leere .content
div, das wir mit den Daten aus den jeweiligen HTML-Dateien der Zeichen ersetzen wollen:
<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>
Ohne JavaScript funktioniert diese Seite immer noch so, wie sie sollte, das Anklicken eines Links führt zur richtigen Seite und das Anklicken des Zurück-Buttons funktioniert auch so, wie ein Benutzer es erwarten würde. Ein Hoch auf Barrierefreiheit und Graceful Degradation!
Als Nächstes springen wir rüber zu JavaScript, wo wir damit beginnen können, jedem Link innerhalb des .gallery
-Elements einen Event-Handler hinzuzufügen, indem wir Event-Propagation verwenden, etwa so:
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);
Innerhalb dieser if
-Anweisung können wir dann das data-name
-Attribut des von uns ausgewählten Bildes der data
-Variable zuweisen. Dann hängen wir „.html“ daran an und verwenden das als dritten Parameter, die URL, die wir laden möchten, in unserer pushState
-Methode (obwohl wir in einem realen Beispiel die URL wahrscheinlich erst ändern wollen, nachdem die Ajax-Anfrage erfolgreich war):
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
(Alternativ könnten wir auch das href-Attribut des Links dafür verwenden.)
Ich habe funktionierenden Code durch Kommentare ersetzt, so dass wir uns jetzt auf die pushState
-Methode konzentrieren können.
Also wird an diesem Punkt ein Klick auf ein Bild die URL-Leiste und den Inhalt mit der Ajax-Anfrage aktualisieren, aber ein Klick auf die Zurück-Schaltfläche wird uns nicht zu dem vorher ausgewählten Zeichen schicken. Was wir hier tun müssen, ist, eine weitere Ajax-Anfrage zu stellen, wenn der Benutzer auf die Schaltfläche „Zurück/Vorwärts“ klickt, und dann müssen wir die URL noch einmal mit pushState
aktualisieren.
Wir gehen zuerst zurück und aktualisieren den State-Parameter unserer pushState
-Methode, um diese Information zu speichern:
history.pushState(data, null, url);
Dies ist der erste Parameter, data
in der Methode oben. Alles, was auf diese Variable gesetzt wird, steht uns nun in einem popstate
-Ereignis zur Verfügung, das immer dann ausgelöst wird, wenn der Benutzer auf die Schaltflächen „Vor“ oder „Zurück“ klickt.
window.addEventListener('popstate', function(e) { // e.state is equal to the data-attribute of the last image we clicked});
Anschließend können wir diese Informationen nach Belieben verwenden, was in diesem Fall bedeutet, dass wir den Namen des vorherigen Ghostbusters, den wir ausgewählt haben, als Parameter an die Ajax requestContent
-Funktion übergeben, die die load
-Methode von jQuery verwendet:
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; }});
Wenn ein Benutzer auf das Bild von Ray klicken würde, würde unser Event-Listener feuern, der dann das data-Attribut unseres Bildes innerhalb des pushState
-Ereignisses speichern würde. Dadurch wird also das ray.html
geladen, das aufgerufen wird, wenn der Benutzer ein anderes Bild auswählt und dann auf den Zurück-Button klickt. *Puh*.
Was bleibt uns damit? Nun, wenn wir auf ein Zeichen klicken und dann die URL freigeben, die wir aktualisiert haben, dann würde stattdessen diese HTML-Datei geladen werden. Das könnte ein weniger verwirrendes Erlebnis sein und wir werden die Integrität unserer URLs bewahren, während wir unseren Benutzern insgesamt ein schnelleres Browsing-Erlebnis bieten.
Es ist wichtig zuzugeben, dass das obige Beispiel sehr vereinfacht ist, da das Laden von Inhalten auf diese Weise mit jQuery sehr chaotisch ist und wir wahrscheinlich ein komplexeres Objekt an unsere pushState
-Methode übergeben wollen würden, aber es zeigt uns, wie wir sofort damit beginnen können, zu lernen, wie man die History-API verwendet. Erst gehen wir, dann laufen wir.
Wenn wir diese Technik in größerem Umfang einsetzen wollen, sollten wir wahrscheinlich ein Tool verwenden, das speziell für diesen Zweck entwickelt wurde. Zum Beispiel ist pjax ein jQuery-Plugin, das den Prozess der gleichzeitigen Verwendung von Ajax und pushState beschleunigt, obwohl es nur Browser unterstützt, die die History-API verwenden.
History JS hingegen unterstützt ältere Browser mit dem alten Hash-Fallback in den URLs.
Cool URLs
Ich mag es, über URLs nachzudenken, und ich beziehe mich besonders auf diesen Beitrag über URL-Design von Kyle Neath die ganze Zeit:
URLs sind universell. Sie funktionieren in Firefox, Chrome, Safari, Internet Explorer, cURL, wget, auf Ihrem iPhone, Android und sogar auf Notizzetteln geschrieben. Sie sind die eine universelle Syntax des Webs. Nehmen Sie das nicht als selbstverständlich hin. Jeder normale, halbwegs technisch versierte Benutzer Ihrer Website sollte in der Lage sein, 90 % Ihrer App anhand der Erinnerung an die URL-Struktur zu navigieren. Um dies zu erreichen, müssen Ihre URLs pragmatisch sein.
Das bedeutet, dass unabhängig von allen Hacks oder leistungssteigernden Tricks, die wir vielleicht implementieren wollen, Webentwickler die URL wertschätzen sollten und mit Hilfe der HTML5 History API können wir Probleme wie das obige Beispiel mit ein wenig Ellenbogenfett beheben.
Gebräuchliche Fehler
- Es ist oft eine gute Idee, den Ort einer Ajax-Anfrage in die
href
-Attribute eines Anker-Elements einzubetten. - Stellen Sie sicher, dass
return true
von Javascript-Klick-Handlern, wenn Leute auf die Mitte oder den Befehl klicken, damit wir sie nicht versehentlich überschreiben.
Weitere Lektüre
- Mozillas Dokumentation zur Manipulation des Browserverlaufs
- Das Ajax-Galerie-Beispiel aus Dive into HTML5
- Twitters Implementierung von pushState
Browser-Unterstützung
Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11,50+ | 10+ | 4,3+ | 7,1+ |