Kurzfassung
Hier ist ein Geheimnis: Du benötigst möglicherweise keine scroll
-Ereignisse in deiner nächsten App. Bei Verwendung eines
IntersectionObserver
,
Ich zeige Ihnen, wie Sie ein benutzerdefiniertes Ereignis auslösen können, wenn position:sticky
-Elemente fixiert werden oder nicht mehr fixiert sind. Und das alles ohne
Scroll-Listener einsetzen. Es gibt sogar eine tolle Demo, um dies zu beweisen:
Jetzt neu: das sticky-change
-Ereignis
Eine der praktischen Einschränkungen bei der CSS-Eigenschaft „fixierte Position“ besteht darin, kein Plattformsignal bereitgestellt, um festzustellen, ob die Unterkunft aktiv ist. Es gibt also kein Ereignis, das weiß, wann ein Element fixiert wird oder klemmt es nicht mehr.
Im folgenden Beispiel wird ein <div class="sticky">
um 10 px vom
oben des übergeordneten Containers:
.sticky {
position: sticky;
top: 10px;
}
Wäre es nicht schön, wenn der Browser Ihnen sagen würde, wenn die Elemente diese Markierung treffen?
Offenbar bin ich nicht die Einzige
das denkt. Ein Signal für position:sticky
könnte eine Reihe von Anwendungsfällen freischalten:
- Wende einen Schlagschatten auf ein fixiertes Banner an.
- Erfassen Sie Analytics-Treffer, während ein Nutzer Ihre Inhalte liest, Fortschritt.
- Wenn ein Nutzer auf der Seite scrollt, aktualisieren Sie ein Floating-TOC-Widget auf die aktuelle .
Unter Berücksichtigung dieser Anwendungsfälle haben wir uns das Ziel gesetzt: ein Ereignis zu erstellen, das
wird ausgelöst, wenn ein position:sticky
-Element fixiert wird. Nennen wir sie die
sticky-change
-Ereignis:
document.addEventListener('sticky-change', e => {
const header = e.detail.target; // header became sticky or stopped sticking.
const sticking = e.detail.stuck; // true when header is sticky.
header.classList.toggle('shadow', sticking); // add drop shadow when sticking.
document.querySelector('.who-is-sticking').textContent = header.textContent;
});
In der Demo wird diesem Ereignis einen Schlagschatten hinzu, wenn die Probleme behoben werden. Außerdem wird die neuen Titel oben auf der Seite.
<ph type="x-smartling-placeholder">Scrolleffekte ohne Scroll-Ereignisse?
<ph type="x-smartling-placeholder">Lassen Sie uns einige Terminologie aus dem Weg räumen, damit ich mich auf diese Namen beziehen kann. im weiteren Verlauf dieses Beitrags:
- Scrollcontainer: der Inhaltsbereich (sichtbarer Darstellungsbereich), der die Liste von „Blogposts“.
- Überschriften: blaue Titel in jedem Abschnitt, in denen
position:sticky
enthalten ist - Fixierte Abschnitte: die einzelnen Inhaltsbereiche. Der Text, der unterhalb der fixierte Überschriften.
- Fixierter Modus: Wenn
position:sticky
auf das Element angewendet wird.
Um zu wissen, welcher header in den fixierten Modus wechselt, müssen wir bestimmen,
Scroll-Offset des Scrollcontainers Damit hätten wir die Möglichkeit,
um den aktuell angezeigten header zu berechnen. Das wird jedoch ziemlich
scroll
-Ereignisse zu vermeiden. Das andere Problem ist,
position:sticky
entfernt das Element aus dem Layout, sobald es fixiert ist.
Ohne Scroll-Ereignisse können Layout-bezogene Berechnungen in den Überschriften.
Dumby-DOM zur Bestimmung der Scrollposition hinzufügen
Anstelle von scroll
-Ereignissen verwenden wir IntersectionObserver
,
bestimmen, wann Header den fixierten Modus aktivieren und beenden. Zwei Knoten hinzufügen
(Sentinels) in jedem fixierten Abschnitt, einen oben und einen
unten dienen als Wegpunkte für die Ermittlung der Scrollposition. Da diese
Markierungen in den Container eintreten oder ihn verlassen, ändert sich ihre Sichtbarkeit
Intersection Observer löst einen Callback aus.
Wir benötigen zwei Sentinels, um vier Fälle des Hoch- und Herunterscrollens abzudecken:
- Nach unten scrollen: Der Header wird fixiert, wenn sich der obere Sentinel kreuzt. am oberen Rand des Containers.
- Scrollen nach unten – header verlässt den fixierten Modus, wenn er das Ende des der Abschnitt und die untere Sentinel überqueren den oberen Bereich des Containers.
- Nach oben scrollen – header verlässt den fixierten Modus, wenn der obere Sentinel gescrollt wird wieder von oben sichtbar.
- Nach oben scrollen – Der Header wird fixiert, wenn der untere Sentinel nach hinten bewegt wird. von oben sichtbar.
Es ist hilfreich, sich einen Screencast von 1 bis 4 in der Reihenfolge anzusehen, in der sie stattfinden:
<ph type="x-smartling-placeholder">Das Preisvergleichsportal
Die Sentinels befinden sich oben und unten in jedem Abschnitt.
.sticky_sentinel--top
befindet sich oben im Header, während
.sticky_sentinel--bottom
steht am Ende des Abschnitts:
:root {
--default-padding: 16px;
--header-height: 80px;
}
.sticky {
position: sticky;
top: 10px; /* adjust sentinel height/positioning based on this position. */
height: var(--header-height);
padding: 0 var(--default-padding);
}
.sticky_sentinel {
position: absolute;
left: 0;
right: 0; /* needs dimensions */
visibility: hidden;
}
.sticky_sentinel--top {
/* Adjust the height and top values based on your on your sticky top position.
e.g. make the height bigger and adjust the top so observeHeaders()'s
IntersectionObserver fires as soon as the bottom of the sentinel crosses the
top of the intersection container. */
height: 40px;
top: -24px;
}
.sticky_sentinel--bottom {
/* Height should match the top of the header when it's at the bottom of the
intersection container. */
height: calc(var(--header-height) + var(--default-padding));
bottom: 0;
}
Beobachten der Kreuzung einrichten
Beobachten der Schnittmenge beobachten asynchron Änderungen an der Schnittmenge ein Zielelement und den Darstellungsbereich des Dokuments oder einen übergeordneten Container. In unserem Fall sehen wir Schnittpunkte mit einem übergeordneten Container.
Die magische Soße ist IntersectionObserver
. Für jeden Sentinel gibt es
IntersectionObserver
, um die Sichtbarkeit der Kreuzung im
Scroll-Container. Wenn ein Sentinel in den sichtbaren Darstellungsbereich scrollt,
eine Kopfzeile fixiert wird oder nicht mehr fixiert ist. Das Gleiche gilt, wenn ein Sentinel
Darstellungsbereich.
Zuerst richte ich Beobachter für die Header- und Fußzeilen-Sentinels ein:
/**
* Notifies when elements w/ the `sticky` class begin to stick or stop sticking.
* Note: the elements should be children of `container`.
* @param {!Element} container
*/
function observeStickyHeaderChanges(container) {
observeHeaders(container);
observeFooters(container);
}
observeStickyHeaderChanges(document.querySelector('#scroll-container'));
Dann habe ich einen Beobachter hinzugefügt, der ausgelöst wird, wenn .sticky_sentinel--top
-Elemente
durch den oberen Scroll-Container (in beide Richtungen).
Mit der Funktion observeHeaders
werden die wichtigsten Sentinels erstellt und den
für jeden Abschnitt. Der Beobachter berechnet den Schnittpunkt der Sentinel mit
des Containers und entscheidet, ob er in den Darstellungsbereich eintritt oder ihn verlässt. Das
wird bestimmt, ob die Abschnittsüberschrift fixiert ist.
/**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--top` become visible/invisible at the top of the container.
* @param {!Element} container
*/
function observeHeaders(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
// Started sticking.
if (targetInfo.bottom < rootBoundsInfo.top) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.bottom >= rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [0], root: container});
// Add the top sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--top');
sentinels.forEach(el => observer.observe(el));
}
Der Beobachter ist mit threshold: [0]
konfiguriert, sodass sein Callback ausgelöst wird, sobald
wenn die Sentinel sichtbar wird.
Der Vorgang ist für den unteren Sentinel (.sticky_sentinel--bottom
) ähnlich.
Ein zweiter Beobachter wird erstellt, der ausgelöst wird, wenn die Fußzeilen den unteren Rand passieren
des scrollbaren Containers. Mit der Funktion observeFooters
wird der
Sentinel-Knoten und fügt sie jedem Abschnitt hinzu. Der Beobachter berechnet die
zwischen der Sentinel und der Unterseite des Containers und entscheidet,
eintreten oder verlassen. Anhand dieser Informationen wird bestimmt,
ob Sie nicht stecken oder nicht.
/**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--bottom` become visible/invisible at the bottom of the
* container.
* @param {!Element} container
*/
function observeFooters(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
const ratio = record.intersectionRatio;
// Started sticking.
if (targetInfo.bottom > rootBoundsInfo.top && ratio === 1) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.top < rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [1], root: container});
// Add the bottom sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--bottom');
sentinels.forEach(el => observer.observe(el));
}
Der Beobachter ist mit threshold: [1]
konfiguriert, sodass sein Callback ausgelöst wird, wenn der
der gesamte Knoten sichtbar ist.
Schließlich gibt es meine beiden Dienstprogramme zum Auslösen des benutzerdefinierten Ereignisses sticky-change
.
und die Sentinels generieren:
/**
* @param {!Element} container
* @param {string} className
*/
function addSentinels(container, className) {
return Array.from(container.querySelectorAll('.sticky')).map(el => {
const sentinel = document.createElement('div');
sentinel.classList.add('sticky_sentinel', className);
return el.parentElement.appendChild(sentinel);
});
}
/**
* Dispatches the `sticky-event` custom event on the target element.
* @param {boolean} stuck True if `target` is sticky.
* @param {!Element} target Element to fire the event on.
*/
function fireEvent(stuck, target) {
const e = new CustomEvent('sticky-change', {detail: {stuck, target}});
document.dispatchEvent(e);
}
Fertig!
Abschließende Demo
Es wurde ein benutzerdefiniertes Ereignis erstellt, wenn Elemente mit position:sticky
zu
Scrolleffekte korrigiert und hinzugefügt, ohne dass scroll
-Ereignisse verwendet wurden.
Fazit
Ich habe mich oft gefragt, ob IntersectionObserver
ein nützliches Tool sein, um einige der scroll
ereignisbasierten UI-Muster zu ersetzen,
im Laufe der Jahre entwickelt. Wie sich herausstellt, lautet die Antwort ja und nein. Die Semantik
der IntersectionObserver
API die Nutzung für alles erschweren. Aber als
Ich habe Ihnen gezeigt, wie Sie damit
ein paar interessante Techniken erlernen können.
Eine andere Möglichkeit, Stiländerungen zu erkennen?
Nicht wirklich. Wir brauchten eine Möglichkeit, Stiländerungen bei einem DOM-Element zu beobachten. Leider gibt es in den Webplattform-APIs nichts, was es Ihnen ermöglicht, Stiländerungen der Smartwatch.
Ein MutationObserver
wäre eine logische erste Wahl, aber das funktioniert nicht für
in den meisten Fällen. In der Demo erhalten wir zum Beispiel einen Callback, wenn der sticky
-Klasse wird einem Element hinzugefügt, jedoch nicht, wenn sich der berechnete Stil des Elements ändert.
Denken Sie daran, dass die Klasse sticky
bereits beim Seitenaufbau deklariert wurde.
In Zukunft wird ein
Style Mutation Observer
Erweiterung auf Mutation Observers kann nützlich sein, um Änderungen an einem
berechneten Stilen des Elements.
position: sticky
.