TL;DR
Oto tajemnica: w następnej aplikacji możesz nie potrzebować zdarzeń scroll
. Za pomocą
IntersectionObserver
,
Pokazuję, jak można wywołać zdarzenie niestandardowe, gdy elementy position:sticky
zostaną naprawione lub przestaną się przyklejać. Wszystko bez
wykorzystanie detektorów przewijania. Dostępnych jest nawet świetna wersja demonstracyjna, która to potwierdza:
Przedstawiamy wydarzenie sticky-change
Jednym z praktycznych ograniczeń stosowania stałej pozycji CSS jest to, że nie dostarcza sygnału platformy, by sprawdzić, czy usługa jest aktywna. Inaczej mówiąc, nie ma zdarzenia, na podstawie którego można by sprawdzić, kiedy element stanie się przyklejony lub kiedy przestaje być już klejony.
W poniższym przykładzie poprawiamy <div class="sticky">
10 pikseli z parametru
górnej części kontenera nadrzędnego:
.sticky {
position: sticky;
top: 10px;
}
Czy nie byłoby miło, gdyby przeglądarka informowała o tym, że elementy dotykają tego znacznika?
Wygląda na to, że nie jestem jedyną osobą
a w konsekwencji Sygnał dla aplikacji position:sticky
może odblokować wiele przypadków użycia:
- Zastosuj cień do przyklejonego banera.
- Gdy użytkownik czyta treści, rejestruj działania analityczne, aby wiedzieć, postęp.
- Gdy użytkownik przewija stronę, aktualizuj pływający widżet TOC do bieżącego .
Mając to na uwadze, opracowaliśmy cel końcowy: zdarzenie
uruchamia się, gdy element position:sticky
zostanie naprawiony. Nazwijmy go
sticky-change
zdarzenie:
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;
});
W wersji demonstracyjnej używamy: to zdarzenie, aby nagłówki były wyświetlane jako cień, gdy zostaną naprawione. Aktualizuje też nowy tytuł u góry strony.
Przewijanie bez zdarzeń przewijania?
![Struktura strony.](https://developer.chrome.com/static/docs/css-ui/sticky-headers/image/structure-the-page-de19d5c111bd1.png?authuser=2&hl=pl)
Żeby nie zapomnieć o tych nazwach, przez resztę posta:
- Kontener przewijany – obszar treści (widoczny widoczny obszar) zawierający listę „postów na blogu”.
- Nagłówki – niebieski tytuł w każdej sekcji z tagiem
position:sticky
. - Sekcje przyklejone – każda sekcja treści. Tekst, który przewija się pod przyklejone nagłówki.
- „Tryb przyklejony” – gdy do elementu stosuje się
position:sticky
.
Aby dowiedzieć się, który nagłówek przechodzi w „tryb przyklejony”, musimy znaleźć sposób
przesunięcie przewijania kontenera przewijania. Dzięki temu moglibyśmy
aby obliczyć wyświetlany nagłówek. Robi się to jednak dość
trudne do wykonania bez zdarzeń scroll
:) Innym problemem jest to,
position:sticky
usuwa element z układu, gdy staje się stały.
Dlatego bez zdarzeń przewijania utraciliśmy możliwość wykonywania działań związanych z układem obliczeniach w nagłówkach.
Dodawanie zastępczego modelu DOM w celu określenia pozycji przewijania
Zamiast zdarzeń scroll
użyjemy IntersectionObserver
do
określać, kiedy nagłówki wchodzą w tryb klawiszy trwałych i kiedy z niego wychodzą. Dodawanie 2 węzłów
(inaczej strażnicy) w każdej przyklejonej sekcji, jednej na górze i drugiej
będzie służyć jako punkty pośrednie do określania pozycji przewijania. Ponieważ te
do wewnątrz i na zewnątrz kontenera, ich widoczność zmienia się
Obserwator segmentów uruchamia wywołanie zwrotne.
![Bez widocznych elementów wartych](https://developer.chrome.com/static/docs/css-ui/sticky-headers/image/without-sentinel-elements-f9529d3483c91.gif?authuser=2&hl=pl)
Potrzebujemy 2 strażników, by uwzględnić 4 przypadki przewijania w górę i w dół:
- Przewijanie w dół – nagłówek przykleja się, gdy jego górna część krzyży się krzyżyk górnej części kontenera.
- Przewijanie w dół – nagłówek opuszcza tryb przyklejony, gdy dochodzi do dołu przekrój i jego dolny strażnik krzyżują się z górną krawędzią kontenera.
- Przewijanie w górę – nagłówek wyłącza tryb klawiszy trwałych, gdy przewijany jest górny czujnik. z powrotem do góry.
- Przewijanie w górę – nagłówek staje się przyklejony, a jego dolna część przesuwa się z powrotem. tak aby było dobrze widoczne z góry.
Warto zobaczyć screencasty 1–4 w kolejności od wystąpienia:
Usługa porównywania cen
Strażnicy znajdują się na górze i na dole każdej sekcji.
Komponent .sticky_sentinel--top
znajduje się na górze nagłówka,
Element .sticky_sentinel--bottom
znajduje się na dole sekcji:
![Dolny poziom wartości zbliża się do progu.](https://developer.chrome.com/static/docs/css-ui/sticky-headers/image/bottom-sentinel-reaching-208a13f07bd7c.png?authuser=2&hl=pl)
: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;
}
Konfiguracja obserwatorów skrzyżowań
Obserwatorzy skrzyżowań asynchronicznie obserwują zmiany na przecięciu elementu docelowego i widocznego obszaru dokumentu lub kontenera nadrzędnego. W naszym przypadku widzimy skrzyżowania z kontenerem nadrzędnym.
Magiczny algorytm to IntersectionObserver
. Każdy strażnik otrzymuje
IntersectionObserver
, aby zaobserwować jego widoczność przecięcia w obrębie
kontener przewijania. Gdy czujnik przewija się w widoczny obszar, wiemy,
naprawiono lub przestał być przyklejony. Podobnie, gdy strażnik opuści
w widocznym obszarze.
Najpierw skonfigurowałem obserwatorów dla strażników nagłówka i stopki:
/**
* 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'));
Następnie dodałem obserwatora, który będzie się uruchamiać, gdy elementy .sticky_sentinel--top
zostaną spełnione
w górnej części kontenera z przewijaniem (w dowolnym kierunku).
Funkcja observeHeaders
tworzy górne strażnicy i dodaje je do
każdej sekcji. Obserwator oblicza przecięcie wag z
kontener i określa, czy wnika on w widoczny obszar czy go opuszcza. To
informacje o tym, czy nagłówek sekcji jest przyklejony, czy nie.
/**
* 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));
}
Obserwator jest skonfigurowany z użyciem funkcji threshold: [0]
, więc jego wywołanie zwrotne uruchamia się natychmiast
w miarę jak strażnik staje się widoczny.
Proces jest podobny w przypadku dolnego strażnika (.sticky_sentinel--bottom
).
Tworzony jest drugi obserwator, który uruchamia się, gdy stopki przechodzą przez dolną część
w kontenerze przewijania. Funkcja observeFooters
tworzy
węzłów ustawodawczych i przyłącza je do każdej sekcji. Obserwator oblicza
przecina się z dnem kontenera i decyduje, czy
wejścia i wyjścia. Ta informacja określa, czy nagłówek sekcji to
przyklejanie, czy nie.
/**
* 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));
}
Obserwator jest skonfigurowany z użyciem funkcji threshold: [1]
, tak więc jego wywołanie zwrotne jest uruchamiane, gdy
cały węzeł jest w widoku.
Do uruchamiania zdarzenia niestandardowego sticky-change
chcę wykorzystać dwa narzędzia:
i generowanie strażników.
/**
* @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);
}
Znakomicie.
Ostatnia wersja demonstracyjna
Utworzyliśmy zdarzenie niestandardowe, gdy elementy z position:sticky
stają się
naprawiono i dodano efekty przewijania bez użycia zdarzeń scroll
.
Podsumowanie
Często zastanawiam się, czy IntersectionObserver
może zastąpić niektóre wzorce interfejsu użytkownika scroll
oparte na zdarzeniach,
rozwijały się na przestrzeni lat. Odpowiedź brzmi: tak i nie. Semantyka
interfejsu API IntersectionObserver
utrudniają korzystanie z nich do wszystkiego. Ale, jak
Pokazaliśmy tutaj, że można go wykorzystać z kilkoma interesującymi technikami.
Inny sposób wykrywania zmian stylu?
Nie bardzo Potrzebowaliśmy sposobu na obserwowanie zmian stylu elementów DOM. Niestety w ramach interfejsów API platformy internetowej nie ma nic, co pozwoliłoby Ci styl zegarka się zmienia.
MutationObserver
to logiczny wybór, ale nie sprawdza się w przypadku:
w większości przypadków. W wersji demonstracyjnej na przykład użytkownik sticky
oddzwoni telefon, gdy
jest dodawana do elementu, ale nie wtedy, gdy zmieni się obliczony styl elementu.
Pamiętaj, że klasa sticky
została już zadeklarowana podczas wczytywania strony.
W przyszłości
„Style Mutation Observer” (Obserwator mutacji stylu).
Obserwatorzy mutacji mogą być przydatne do obserwowania zmian
w obliczanych stylach elementu.
position: sticky
.