Przedstawiamy VisualViewport

Jake Archibald
Jake Archibald

A jeśli wiesz, że jest więcej niż 1 widoczny obszar?

BRRRRAAAAAAAMMMMMMMM

Używany w tej chwili widoczny obszar jest w rzeczywistości widocznym obszarem w obrębie widocznego obszaru.

BRRRRAAAAAAAMMMMMMMM

Czasem dane, które dostarcza DOM, odnoszą się do jednego z tych widocznych obszarów, a nie do drugiego.

BRRRRAAAAM... Co robić?

To prawda, spójrz na to:

Widoczna część układu i widoczna

Powyższy film przedstawia przewijaną i powiększoną stronę internetową z minimapą po prawej stronie, pokazującą umiejscowienie widocznych na niej widocznych obszarów.

Podczas normalnego przewijania strony wszystko idzie do przodu. Zielony obszar reprezentuje widoczny obszar układu, w którym są przyklejone position: fixed elementy.

Po wprowadzeniu powiększania/ściągania palcami sytuacja staje się dziwna. Czerwone pole przedstawia widoczny obszar, czyli część strony, którą widzimy. Ten widoczny obszar może się poruszać, gdy elementy position: fixed pozostają tam, gdzie się znajdowały, dołączone do widocznego obszaru układu. Jeśli przesuniesz widok na granicę widocznego obszaru układu, zacznie się on przesuwać razem z nim.

Poprawa zgodności

Internetowe interfejsy API mają niespójność pod względem tego, do którego widocznego obszaru się odnoszą, a także niespójności w różnych przeglądarkach.

Na przykład element.getBoundingClientRect().y zwraca przesunięcie w widocznym obszarze układu. To fajne, ale często zależy nam na pozycji na stronie, więc piszemy:

element.getBoundingClientRect().y + window.scrollY

Wiele przeglądarek korzysta jednak z widocznego obszaru w przypadku window.scrollY, co oznacza, że powyższy kod ulega uszkodzeniu, gdy użytkownik pomniejsza widok palcami.

Chrome 61 zmienia parametr window.scrollY, by zamiast tego odnosił się do widocznego obszaru układu. Oznacza to, że powyższy kod działa nawet po powiększeniu lub pomniejszeniu widoku ściągnięciem palców. Przeglądarki powoli zmieniają wszystkie właściwości pozycji, aby odnosić się do widocznego obszaru układu.

Z wyjątkiem jednej nowej usługi...

Wyświetlanie widocznego obszaru dla skryptu

Nowy interfejs API ujawnia widoczny obszar jako window.visualViewport. To wersja robocza specyfikacji, zatwierdzona w różnych przeglądarkach, dostępna w Chrome 61.

console.log(window.visualViewport.width);

window.visualViewport daje nam dostęp do takich korzyści:

visualViewport miejsca zakwaterowania
offsetLeft Odległość między lewą krawędzią widocznego obszaru a widocznym obszarem układu wyrażona w pikselach CSS.
offsetTop Odległość między górną krawędzią widocznego obszaru a widocznym obszarem układu wyrażona w pikselach CSS.
pageLeft Odległość między lewą krawędzią widocznego obszaru a lewą granicą dokumentu podana w pikselach CSS.
pageTop Odległość między górną krawędzią widocznego obszaru a górną granicą dokumentu podana w pikselach CSS.
width Szerokość widocznego obszaru wizualnego w pikselach CSS.
height Wysokość widocznego obszaru w pikselach CSS.
scale Skala stosowana przez powiększanie ściąganiem palców. Jeśli treść jest dwukrotnie większa z powodu powiększenia, wartość zwraca wartość 2. devicePixelRatio nie ma na to wpływu.

Jest też kilka zdarzeń:

window.visualViewport.addEventListener('resize', listener);
visualViewport zdarzenia
resize Uruchamiane po zmianie właściwości width, height lub scale.
scroll Uruchamiane, gdy zmieni się offsetLeft lub offsetTop.

Wersja demonstracyjna

Film na początku tego artykułu został stworzony przy użyciu visualViewport. Obejrzyj go w Chrome 61 i nowszych wersjach. Dzięki visualViewport miniaturka przykleja się do prawego górnego rogu widocznego obszaru. Stosuje odwrotną skalę, dzięki której wyświetla się zawsze w tym samym rozmiarze, nawet jeśli powiększysz obraz przez ściąganie palców.

Gotchas

Zdarzenia uruchamiają się tylko po zmianie widocznego obszaru

To dość oczywiste, ale wciągnęło mnie to, gdy pierwszy raz grałam w visualViewport.

Jeśli widoczny obszar układu zmieni się, ale widoczny obszar wizualny się nie zmieni, nie otrzymasz zdarzenia resize. Rzadko zmienia się jednak rozmiar widocznego obszaru w układzie bez zmiany szerokości lub wysokości widocznego obszaru.

Prawdziwa matka się przewija. Jeśli nastąpi przewijanie, ale widoczny obszar pozostanie statyczny w stosunku do widocznego obszaru układu, zdarzenie scroll nie zostanie zarejestrowane w visualViewport. Jest to bardzo częste. Podczas zwykłego przewijania dokumentu widoczny obszar pozostaje zablokowany w lewym górnym rogu widocznego obszaru układu, więc scroll nie uruchamia się w visualViewport.

Jeśli chcesz poznać wszystkie zmiany w widocznym obszarze, w tym pageTop i pageLeft, musisz też nasłuchiwać zdarzenia przewijania w oknie:

visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);

Unikaj powielania pracy z wieloma słuchaczami

Podobnie jak w przypadku słuchania w oknie poleceń scroll i resize, prawdopodobnie wywołasz pewną funkcję „update”. Często jednak te zdarzenia występują w tym samym czasie. Jeśli użytkownik zmieni rozmiar okna, wywoła to resize, ale często też scroll. Aby poprawić skuteczność, unikaj wielokrotnego wprowadzania zmian:

// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);

let pendingUpdate = false;

function update() {
    // If we're already going to handle an update, return
    if (pendingUpdate) return;

    pendingUpdate = true;

    // Use requestAnimationFrame so the update happens before next render
    requestAnimationFrame(() => {
    pendingUpdate = false;

    // Handle update here
    });
}

Zgłosiłem problem ze specyfikacją, ponieważ wydaje mi się, że może być inny, lepszy sposób, na przykład użycie pojedynczego zdarzenia update.

Moduły obsługi zdarzeń nie działają

Ze względu na błąd w Chrome to nie działa:

Nie

Błąd – korzysta z modułu obsługi zdarzeń

visualViewport.onscroll = () => console.log('scroll!');

Zamiast tego:

Tak

Działa – używa detektora zdarzeń

visualViewport.addEventListener('scroll', () => console.log('scroll'));

Wartości przesunięcia są zaokrąglane

Wydaje mi się, że to kolejny błąd w Chrome.

Wartości offsetLeft i offsetTop są zaokrąglane, co może być niedokładne, gdy użytkownik powiększy widok. Problemy z tym problemem możesz zauważyć w wersji demonstracyjnej. Jeśli użytkownik powiększa obraz i przesuwa go powoli, migawki minimapy między niepowiększonymi pikselami.

Częstotliwość zdarzeń jest niska

Podobnie jak inne zdarzenia resize i scroll, nie uruchamiają się one każdej klatki, zwłaszcza na urządzeniach mobilnych. Widać to w wersji demonstracyjnej – gdy powiększysz obraz, miniatura ma problem z utrzymaniem jej w widocznym obszarze.

Ułatwienia dostępu

W wersji demonstracyjnej udało mi się użyć visualViewport, aby zapobiec powiększeniu lub ściągnięciu palcami przez użytkownika. To ma sens w przypadku tej konkretnej prezentacji, ale dobrze się zastanów, zanim zrobisz coś, co zastąpi zamiar użytkownika, aby powiększyć widok.

Aby poprawić ułatwienia dostępu, można użyć aplikacji visualViewport. Jeśli na przykład użytkownik powiększa widok, możesz ukryć przedmioty dekoracyjne position: fixed, by nie przeszkadzały użytkownikowi. Pamiętaj jednak, że nie ukrywaj tego, czego użytkownik chce się mu przyjrzeć.

Możesz rozważyć opublikowanie posta w usłudze analitycznej, gdy użytkownik powiększy widok. Może Ci to pomóc zidentyfikować strony, z którymi użytkownicy mają problemy przy domyślnym powiększeniu.

visualViewport.addEventListener('resize', () => {
    if (visualViewport.scale > 1) {
    // Post data to analytics service
    }
});

Gotowe! visualViewport to krótki interfejs API, który po drodze rozwiązuje problemy ze zgodnością.