Die HTML5 History API gibt Entwicklern die Möglichkeit, die URL einer Website zu ändern, ohne die Seite komplett neu zu laden. Dies ist besonders nützlich zum Laden von Teilen einer Seite mit JavaScript, so dass der Inhalt deutlich unterschiedlich ist und eine neue URL rechtfertigt.
Hier ist ein Beispiel. Nehmen wir an, eine Person navigiert von der Homepage einer Website zur Hilfeseite. Wir laden den Inhalt dieser Hilfeseite mit Ajax. Dann geht der Benutzer zur Produktseite, die wir wieder laden und den Inhalt mit Ajax austauschen. Dann möchte er die URL teilen. Mit der History API könnten wir die URL der Seite mit dem Benutzer synchron geändert haben, während er navigiert, so dass die angezeigte (und somit geteilte oder gespeicherte) URL relevant und korrekt ist.
Die Grundlagen
Um die Funktionen dieser API zu überprüfen, gehen Sie einfach in die Entwicklertools und geben Sie history in die Konsole ein. Wenn die API in Ihrem Browser Ihrer Wahl unterstützt wird, finden wir eine Reihe von Methoden, die diesem Objekt zugeordnet sind.

Wir interessieren uns in diesem Tutorial für die Methoden pushState und replaceState. Zurück in der Konsole können wir ein wenig mit den Methoden experimentieren und sehen, was mit der URL passiert, wenn wir sie verwenden. Die anderen Parameter dieser Funktion werden wir später behandeln, aber für den Moment brauchen wir nur den letzten Parameter.
history.replaceState(null, null, 'hello');
Die obige Methode replaceState ersetzt die URL in der Adressleiste durch '/hello', obwohl keine Assets angefordert wurden und das Fenster auf derselben Seite bleibt. Doch hier gibt es ein Problem. Wenn wir auf die Zurück-Schaltfläche klicken, stellen wir fest, dass wir nicht zur URL dieses Artikels zurückkehren, sondern stattdessen *zurück* zu der Seite gehen, auf der wir uns vorher befanden. Das liegt daran, dass replaceState die Browser-Historie nicht manipuliert, sondern einfach die aktuelle URL in der Adressleiste ersetzt.
Um dies zu beheben, müssen wir stattdessen die Methode pushState verwenden.
history.pushState(null, null, 'hello');
Wenn wir nun auf die Zurück-Schaltfläche klicken, sollte sie wie gewünscht funktionieren, da pushState unsere Historie so geändert hat, dass die URL, die wir gerade übergeben haben, enthalten ist. Das ist interessant, aber was passiert, wenn wir etwas hinterhältiges versuchen und vorgeben, dass die aktuelle URL überhaupt nicht css-tricks.com war, sondern eine ganz andere Website?
history.pushState(null, null, 'https://twitter.com/hello');
Dies führt zu einer Ausnahme, da die URL vom **gleichen Ursprung** wie die aktuelle sein muss, sonst riskieren wir größere Sicherheitslücken und geben Entwicklern die Möglichkeit, Leute zu täuschen und sie glauben zu machen, sie wären auf einer ganz anderen Website.
Zurück zu den anderen Parametern, die an diese Methode übergeben werden, können wir sie wie folgt zusammenfassen:
history.pushState([data], [title], [url]);
- Der erste Parameter sind die Daten, die wir benötigen, wenn sich der Zustand der Webseite ändert, zum Beispiel wenn jemand die Vorwärts- oder Rückwärtstaste seines Browsers drückt. Beachten Sie, dass diese Daten in Firefox auf 640.000 Zeichen begrenzt sind.
titleist 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.
Ein kurzer Überblick
Das Wesentlichste an diesen History-APIs ist, dass sie die Seite nicht neu laden. Früher war die einzige Möglichkeit, die URL zu ändern, die Änderung von window.location, was immer die Seite neu geladen hat. Es sei denn, man hat nur den Hash geändert (wie beim Klicken auf einen <a href="#target">Link</a>, der die Seite nicht neu lädt).
Dies führte zur alten Hashbang-Methode, um die URL ohne vollständiges Seiten-Neuladen zu ändern. Berühmterweise hat Twitter dies früher so gemacht und wurde dafür weitgehend kritisiert (ein Hash ist keine "echte" Ressourcen-Adresse).
Twitter hat sich davon verabschiedet und war einer der frühen Befürworter dieser API. Im Jahr 2012 beschrieb das Team ihren neuen Ansatz. Hier legen sie einige ihrer Probleme bei der Arbeit in diesem Maßstab dar und beschreiben gleichzeitig, wie verschiedene Browser diese Spezifikation implementieren.
Ein Beispiel mit pushState und Ajax
Bauen wir eine Demo!
In unserer imaginären Benutzeroberfläche möchten wir, dass die Benutzer unserer Website Informationen über einen Charakter aus Ghostbusters finden. Wenn sie ein Bild auswählen, muss der Text über diesen Charakter darunter erscheinen und wir möchten jedem Bild eine aktuelle Klasse hinzufügen, damit klar ist, wer ausgewählt wurde. Wenn wir dann auf die Zurück-Schaltfläche klicken, springt die aktuelle Klasse zum zuvor ausgewählten Charakter (und umgekehrt für die Vorwärts-Schaltfläche) und natürlich müssen wir auch den darunter liegenden Inhalt wieder wechseln.
Hier ist ein funktionierendes Beispiel, das wir analysieren können.

Das Markup für dieses Beispiel ist einfach genug: Wir haben eine .gallery, die einige Links enthält, und darin jeweils ein Bild. Dann haben wir den darunter liegenden Text, den wir mit dem ausgewählten Namen aktualisieren wollen, und die leere .content div, die wir mit den Daten aus den jeweiligen HTML-Dateien der Charaktere 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 wie erwartet, das Klicken auf einen Link führt zur richtigen Seite und das Klicken auf die Zurück-Schaltfläche funktioniert ebenfalls wie ein Benutzer es erwarten würde. Hurra für Barrierefreiheit und einwandfreie Abwärtskompatibilität!
Als Nächstes springen wir zu JavaScript, wo wir mit Ereignisweiterleitung (event propagation) einen Ereignishandler zu jedem Link innerhalb des .gallery-Elements hinzufügen können, wie folgt:
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 ausgewählten Bildes der Variablen data zuweisen. Dann hängen wir ".html" daran an und verwenden dies als dritten Parameter, die URL, die wir laden möchten, in unserer pushState-Methode (obwohl wir in einem echten Beispiel die URL wahrscheinlich nur *nachdem* die Ajax-Anfrage erfolgreich war, ändern würden).
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, damit wir uns vorerst auf die Methode pushState konzentrieren können.
An diesem Punkt wird durch das Klicken auf ein Bild die Adressleiste und der Inhalt mit der Ajax-Anfrage aktualisiert, aber durch das Klicken auf die Zurück-Schaltfläche gelangen wir nicht zum zuvor ausgewählten Charakter. Was wir hier tun müssen, ist, eine weitere Ajax-Anfrage zu machen, wenn der Benutzer auf die Vorwärts-/Rückwärtstaste klickt, und dann müssen wir die URL mit pushState erneut aktualisieren.
Wir gehen zunächst zurück und aktualisieren den Zustandsparameter unserer pushState-Methode, um diese Informationen zwischenzuspeichern.
history.pushState(data, null, url);
Dies ist der erste Parameter, data, in der obigen Methode. Nun ist alles, was dieser Variablen zugewiesen wird, in einem popstate-Ereignis zugänglich, das ausgelöst wird, wenn der Benutzer auf die Vorwärts- oder Rückwärtstasten klickt.
window.addEventListener('popstate', function(e) {
// e.state is equal to the data-attribute of the last image we clicked
});
Folglich können wir diese Informationen dann nach Belieben verwenden, in diesem Fall übergeben wir den Namen des zuvor ausgewählten Ghostbusters als Parameter in die Ajax-Funktion requestContent, 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 Ereignis-Listener ausgelöst, der dann das Datenattribut unseres Bildes innerhalb des pushState-Ereignisses speichert. Folglich wird die Datei ray.html geladen, die aufgerufen wird, wenn der Benutzer ein anderes Bild auswählt und dann auf die Zurück-Schaltfläche klickt. *Puh*.
Was bleibt uns also übrig? Nun, wenn wir auf einen Charakter klicken und die aktualisierte URL teilen, würde diese HTML-Datei stattdessen geladen werden. Es könnte eine weniger verwirrende Erfahrung sein und wir bewahren die Integrität unserer URLs, während wir unseren Benutzern insgesamt eine schnellere Browsererfahrung bieten.
Es ist wichtig anzuerkennen, dass das obige Beispiel vereinfacht ist, da das Laden von Inhalten auf diese Weise mit jQuery sehr unordentlich ist und wir wahrscheinlich ein komplexeres Objekt in unsere pushState-Methode übergeben würden, aber es zeigt uns, wie wir sofort damit beginnen können, die History API zu lernen. Zuerst gehen wir, dann rennen wir.
Der nächste Schritt
Wenn wir diese Technik im größeren Maßstab anwenden würden, sollten wir wahrscheinlich ein Werkzeug in Betracht ziehen, das speziell für diesen Zweck entwickelt wurde. Zum Beispiel beschleunigt pjax, ein jQuery-Plugin, den Prozess der gleichzeitigen Nutzung von Ajax und pushState, unterstützt aber nur Browser, die die History API verwenden.
History JS hingegen unterstützt ältere Browser mit dem alten Hash-Fallback in den URLs.
Coole URLs
Ich denke gerne über URLs nach und verweise immer wieder auf diesen Beitrag über URL-Design von Kyle Neath.
URLs sind universell. Sie funktionieren in Firefox, Chrome, Safari, Internet Explorer, cURL, wget, deinem iPhone, Android und sogar auf Haftnotizen geschrieben. Sie sind die einzige universelle Syntax des Webs. Nimm das nicht als selbstverständlich hin. Jeder halbwegs technisch versierte Nutzer deiner Seite sollte 90 % deiner App anhand der URL-Struktur auswendig navigieren können. Um dies zu erreichen, müssen deine URLs pragmatisch sein.
Das bedeutet, dass Webentwickler die URL schätzen sollten, unabhängig von Hacks oder leistungssteigernden Tricks, die wir vielleicht implementieren wollen, und mit Hilfe der HTML5 History API können wir Probleme wie das obige Beispiel mit etwas Mühe beheben.
Häufige Fallstricke
- Es ist oft eine gute Idee, den Ort einer Ajax-Anfrage in den
href-Attributen eines Anker-Elements einzubetten. - Stellen Sie sicher, dass Sie von JavaScript-Klick-Handlern mit
return truezurückkehren, wenn Personen mit der mittleren Maustaste oder der Befehlstaste klicken, damit wir sie nicht versehentlich überschreiben.
Weitere Lektüre
- Mozillas Dokumentation zum Manipulieren der Browser-Historie
- 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+ |
Ich liebe diese API so sehr. Sie eröffnet so viele kreative Kanäle. Wir haben sie kürzlich in unserer Paginierung verwendet, sodass die nächste Seite super schnell und normalerweise ohne Verzögerung geladen zu werden scheint. Es gibt viele Faktoren zu berücksichtigen. Schauen Sie sich diese Seite für Informationen zur Analyse an: http://davidwalsh.name/ajax-analytics.
Hallo Robin, ich bin ziemlich sicher, dass Opera das lange vor "26+" unterstützt hat, wie in deiner Browser-Support-Tabelle am Ende steht.
Tatsächlich zeigt selbst in v12.17 die Eingabe von
historyin die Konsole, wie du sagst, die MethodenpushState/replaceStatean.Guter Fang, Chris. Ich habe es über caniuse überprüft, aber laut deren Blog war Opera 11.50 die erste Version. Ich werde den Beitrag aktualisieren, danke!
Opera 11.5 fügte pushState hinzu, aber diese alten Presto-Operas sind jetzt irrelevant.
Ich benutze das hier in meinem neuen Projekt http://tldwhois.truelabs.info/dot-tips
Ich fand es etwas seltsam zu sehen, dass bei den Methoden
pushStateundreplaceStatedas "title"-Argument in Browsern nicht unterstützt wird. Könnten wir also so etwas tun wie? (Ich habe die Funktion addState genannt, aber sie könnte auch anders genannt werden.)
Darf ich auf eine weitere Bibliothek hinweisen, die den Hashbang-Fallback für Browser enthält, die das History-Objekt nicht unterstützen?
Funktioniert wie ein Zauber!
https://github.com/devote/HTML5-History-API
Kleiner Tippfehler oben, 'so sie URL sie sehen (und somit teilen oder speichern) ist relevant und korrekt.'
Insgesamt ein wunderschön geschriebenes Stück, bravo.
Danke Nicky, ich habe den Tippfehler gerade behoben.
"Es ist oft eine gute Idee, den Ort einer Ajax-Anfrage in den href-Attributen eines Anker-Elements einzubetten."
Ein Muss für Google-Bots, damit sie Links folgen und alle Seiten indizieren können, sonst werden diese Seiten nicht indiziert, richtig?
Nein, Google-Bots unterstützen grundlegendes JS schon seit einer Weile.
Nein, Google-Bots unterstützen grundlegendes JS schon seit einer Weile.
Dir fehlt ein Zeichen im Link zur Twitter pushState-Implementierung.
Sollte sein https://blog.twitter.com/2012/implementing-pushstate-for-twittercom
Schöner Artikel
Danke! Ich habe den Link jetzt korrigiert.
Wenn es Hashtags innerhalb des Inhalts gibt, wird die Funktion window.addEventLister mit dem Zustand character == null ausgelöst. Gibt es eine Möglichkeit, das zu vermeiden?