Scroll-Position am unteren Ende fixieren

Unter den Top 5 der nervigsten Dinge, die eine Website tun kann, ist wohl Folgendes: Man versucht gerade etwas zu lesen (oder anzuklicken!) und plötzlich verschiebt sich die Seite unter einem (oder man klickt daneben!). Es gibt eine CSS-Eigenschaft, die helfen kann, das zu beheben. Außerdem können wir sie zweckentfremden, um etwas zu erreichen, das bisher ausschließlich JavaScript vorbehalten war.

Die overflow-anchor Eigenschaft in CSS ist relativ neu; sie debütierte 2017 in Chrome¹, 2019 in Firefox und wurde nun auch von Edge im Zuge des Wechsels zu Chrome im Jahr 2020 übernommen. Glücklicherweise ist ihre Verwendung weitgehend eine Optimierung. Die Idee dahinter ist, dass Browser standardmäßig wirklich versuchen, Positionsverschiebungen zu vermeiden. Wenn einem die Handhabung nicht gefällt, kann man dies mit overflow-anchor ausschalten. Im Normalfall rührt man es also nie an.

Aber wie Sie vielleicht vermuten, können wir dieses kleine Prachtstück für eine kleine CSS-Trickserei ausnutzen. Wir können ein scrollbares Element zwingen, am unteren Ende fixiert zu bleiben, selbst wenn wir neuen Inhalt anhängen.

Ein Chat ist ein klassisches Beispiel für das Fixieren am unteren Ende (Pin-to-Bottom).

Wir erwarten dieses Verhalten in einer Benutzeroberfläche wie Slack: Wenn wir zu den neuesten Nachrichten in einem Kanal hinuntergescrollt haben, sollen neue Nachrichten sofort unten sichtbar sein, ohne dass wir manuell erneut nach unten scrollen müssen, um sie zu sehen.

Dieses Feature stammt von Ryan Hunt, der wiederum Nicolas Chevobbe als Quelle nennt.

Wie Ryan sagt:

Haben Sie jemals versucht, ein scrollbares Element zu implementieren, bei dem neuer Inhalt hinzugefügt wird und Sie den Benutzer am unteren Ende fixieren wollen? Es ist nicht trivial, das korrekt umzusetzen.

Minimum benötigen Sie JavaScript, um zu erkennen, wenn neuer Inhalt hinzugefügt wird, und um das Scrollelement nach unten zu zwingen. Hier ist eine Technik, die MutationObserver in JavaScript verwendet, um auf neuen Inhalt zu achten und das Scrollen zu erzwingen.

const scrollingElement = document.getElementById("scroller");

const config = { childList: true };

const callback = function (mutationsList, observer) {
  for (let mutation of mutationsList) {
    if (mutation.type === "childList") {
      window.scrollTo(0, document.body.scrollHeight);
    }
  }
};

const observer = new MutationObserver(callback);
observer.observe(scrollingElement, config);

Hier ist eine Demo dazu.

Aber ich finde eine reine CSS-Lösung weitaus verlockender! Die obige Version hat einige UX-Fallstricke, auf die wir später noch eingehen werden.

Der Trick dabei ist, dass Browser standardmäßig bereits Scroll-Anchoring betreiben. Aber die Browser versuchen dabei, die Seite nicht unter Ihnen zu verschieben. Wenn also neuer Inhalt hinzugefügt wird, der die visuelle Position der Seite verändern würde, versuchen sie, dies zu verhindern. In diesem ungewöhnlichen Fall wollen wir quasi das Gegenteil. Wir wollen, dass die Seite am unteren Rand "klebt" und sich die Ansicht visuell mitbewegt, weil sie gezwungen ist, am Ende fixiert zu bleiben.

So funktioniert der Trick. Zuerst etwas HTML innerhalb eines scrollbaren Elternelements:

<div id="scroller">
  <!-- new content dynamically inserted here -->
  <div id="anchor"></div>
</div>

Alle Elemente haben von Natur aus ein overflow-anchor: auto;, was bedeutet, dass sie versuchen, das Verschieben der Seite zu verhindern, wenn sie auf dem Bildschirm sichtbar sind. Wir können dies jedoch mit overflow-anchor: none; ausschalten. Der Trick besteht also darin, alle dynamisch eingefügten Inhalte anzuvisieren und die Funktion dort zu deaktivieren:

#scroller * {
  overflow-anchor: none;
}

Dann zwingen wir nur dieses eine Anker-Element dazu, das Scroll-Anchoring zu übernehmen, sonst nichts:

#anchor {
  overflow-anchor: auto;
  height: 1px;
}

Sobald dieser Anker auf der Seite sichtbar ist, wird der Browser gezwungen, die Scrollposition daran zu fixieren. Da er das allerletzte Element in diesem Scrollelement ist, bleibt die Ansicht am unteren Ende fixiert.

Los geht's!

Es gibt hier zwei kleine Vorbehalte…

  1. Beachten Sie, dass der Anker eine gewisse Größe haben muss. Wir verwenden hier height, um sicherzustellen, dass es kein kollabiertes/leeres Element ohne Größe ist, was verhindern würde, dass dies funktioniert.
  2. Damit das Scroll-Anchoring funktioniert, muss die Seite zu Beginn mindestens einmal gescrollt worden sein.

Der zweite Punkt ist schwieriger. Eine Option ist, sich gar nicht darum zu kümmern; man wartet einfach, bis der Benutzer einmal nach unten scrollt, und von da an funktioniert der Effekt. Das ist eigentlich ganz nett, denn wenn man vom unteren Ende wegscrollt, hört der Effekt auf zu wirken – was genau das ist, was man meistens will. In der JavaScript-Version oben fällt auf, wie man zum unteren Ende gezwungen wird, selbst wenn man versucht, nach oben zu scrollen – genau das meinte Ryan damit, dass es nicht trivial ist, es korrekt umzusetzen.

Falls Sie den Effekt sofort benötigen, ist der Trick, das Scrollelement von Anfang an scrollbar zu machen, zum Beispiel so:

body {
  height: 100.001vh;
}

Und dann sofort einen ganz minimalen Scroll-Vorgang auszulösen:

document.scrollingElement.scroll(0, 1);

Das sollte genügen. Diese Zeilen sind in der obigen Demo vorhanden und können zum Ausprobieren einkommentiert werden.

  1. Apropos Chrome: Google nimmt diese Layout-Verschiebungen sehr ernst. Einer der Web Core Vitals ist der Cumulative Layout Shift (CLS), der die visuelle Stabilität misst. Ein schlechter CLS-Wert wirkt sich buchstäblich negativ auf Ihr SEO aus.