Verwendung der HTML5 History API

Avatar of Robin Rendle
Robin Rendle am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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.

Dies sind die uns zur Verfügung stehenden Methoden zur Manipulation der Browser-Historie.

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]);
  1. 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.
  2. title ist der zweite Parameter, der ein String sein kann, aber zum Zeitpunkt des Schreibens ignoriert ihn jeder Browser einfach.
  3. 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.

An example project showing how we might use the History API

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 true zurückkehren, wenn Personen mit der mittleren Maustaste oder der Befehlstaste klicken, damit wir sie nicht versehentlich überschreiben.

Weitere Lektüre

Browser-Unterstützung

Chrome Safari Firefox Opera IE Android iOS
31+ 7.1+ 34+ 11.50+ 10+ 4.3+ 7.1+