Przedstawiamy VisualViewport

Jake Archibald
Jake Archibald

A co, jeśli powiem Ci, że istnieje więcej niż 1 widok?

BRRRRAAAAAAAMMMMMMMMMM

Widoczny obszar, z którego korzystasz, jest widocznym obszarem w widocznym obszarze.

BRRRRAAAAAAAMMMMMMMMMM

Czasami dane z DOM odnoszą się do jednej z tych widocznych stron, a nie do drugiej.

BRRRRAAAAM… poczekaj, co?

To prawda, zobacz:

Widzialny obszar a widoczny obszar

Film powyżej pokazuje przewijanie strony internetowej i powiększanie jej za pomocą gestów, a po prawej stronie minimapę z położeniem widoku na stronie.

Podczas zwykłego przewijania wszystko jest proste. Zielona część przedstawia strefa widoczna układu, do której przylegają elementy position: fixed.

Gdy włączysz powiększanie przez zbliżanie i oddalanie, może się to dziwnie skończyć. Czerwone pole to widoczny widok strony, czyli część strony, którą widzi użytkownik. Widziany obszar może się przemieszczać, a elementy position: fixed pozostają na swoich miejscach, przytwierdzone do widocznego obszaru układu. Jeśli przesuniemy widok na krawędzi układu, przesuniemy też widoczny obszar układu.

Poprawianie zgodności

Niestety interfejsy API stron internetowych nie są spójne pod względem tego, do której przeglądarki odnoszą się w przypadku widoku.

Na przykład element.getBoundingClientRect().y zwraca przesunięcie w widocznym obszarze układu. To świetnie, ale często chcemy znać pozycję na stronie, więc piszemy:

element.getBoundingClientRect().y + window.scrollY

Jednak wiele przeglądarek używa widocznego obszaru viewport dla window.scrollY, co oznacza, że powyższy kod przestaje działać, gdy użytkownik powiększy obraz.

W Chrome 61 element window.scrollY odwołuje się do obszaru widoku układu, co oznacza, że powyższy kod działa nawet po powiększeniu za pomocą gestów. W rzeczywistości przeglądarki powoli zmieniają wszystkie właściwości pozycyjne, aby odwoływały się do widocznego obszaru w układzie.

Z wyjątkiem jednej nowej usługi…

Wyświetlanie widocznego obszaru w skrypcie

Nowy interfejs API udostępnia wizualny widoczny obszar jako window.visualViewport. Jest to specyfikacja wstępna, która ma zatwierdzenie w wielu przeglądarkach i zostanie wprowadzona w Chrome 61.

console.log(window.visualViewport.width);

Oto, co daje nam window.visualViewport:

visualViewport miejsca zakwaterowania
offsetLeft Odległość między lewą krawędzią obszaru widocznego a obszarem widoku układu w pikselach CSS.
offsetTop Odległość między górną krawędzią widocznego obszaru a obszarem układu w pikselach CSS.
pageLeft Odległość między lewą krawędzią wizualnego obszaru widoku a lewą granicą dokumentu w pikselach CSS.
pageTop Odległość między górną krawędzią widocznego obszaru a górną granicą dokumentu w pikselach CSS.
width Szerokość wizualnego obszaru widocznego w pikselach CSS.
height Wysokość wizualnego obszaru widoku w pikselach CSS.
scale Powiększenie zastosowane przez ściągnięcie palców. Jeśli z powodu powiększenia treści są one dwa razy większe, zwróci to wartość 2. Nie ma to wpływu na devicePixelRatio.

Jest też kilka zdarzeń:

window.visualViewport.addEventListener('resize', listener);
visualViewport zdarzenia
resize Uruchamiane, gdy zmieniają się wartości width, height lub scale.
scroll Uruchamiane, gdy offsetLeft lub offsetTop się zmieni.

Prezentacja

Film na początku tego artykułu został utworzony za pomocą visualViewport. Sprawdź go w Chrome 61 lub nowszej wersji. Używa on visualViewport, aby minimapa była przyklejona do prawego górnego rogu widocznego obszaru obrazu, i zachowuje odwrotną skalę, dzięki czemu zawsze ma ten sam rozmiar, mimo powiększania za pomocą pinch-zoom.

Gotcha

zdarzenia są wywoływane tylko wtedy, gdy zmienia się wizualny widoczny obszar;

Może się to wydawać oczywiste, ale mnie to zaskoczyło, gdy po raz pierwszy użyłem visualViewport.

Jeśli rozmiar widoku układu się zmieni, ale nie zmieni się widoczny widok, nie otrzymasz zdarzeniaresize. Zmiana rozmiaru widocznego obszaru układu bez zmiany szerokości/wysokości widocznego obszaru jest jednak rzadka.

Prawdziwy problem to przewijanie. Jeśli nastąpi przewijanie, ale wizualny widoczny obszar pozostanie statyczny w stosunku do widocznego obszaru układu, nie otrzymasz zdarzenia scrollvisualViewport. Jest to bardzo częsta sytuacja. Podczas zwykłego przewijania dokumentu widoczny widoczny obszar pozostaje zablokowany w lewym górnym rogu widocznego obszaru układu, więc scroll nie jest wywoływany w przypadku visualViewport.

Jeśli chcesz otrzymywać informacje o wszystkich zmianach w wizualnym widoku, w tym o pageToppageLeft, musisz też słuchać zdarzenia przewijania okna:

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

Unikanie powielania pracy z wieloma odbiorcami

Podobnie jak w przypadku funkcji scrollresize w ramach okna, prawdopodobnie wywołasz jakąś funkcję „update”. Jednak wiele z tych zdarzeń może występować jednocześnie. Jeśli użytkownik zmieni rozmiar okna, zostanie to zainicjowane przez resize, ale często też przez scroll. Aby zwiększyć wydajność, unikaj wielokrotnego przetwarzania zmiany:

// 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
    });
}

W związku z tym zgłosiłem problem ze specyfikacją, ponieważ uważam, że istnieje lepszy sposób, np. pojedyncze zdarzenie update.

Moduł obsługi zdarzeń nie działa

Z powodu błędu w Chrome nie działa:

Nie

Wadliwe – używa modułu obsługi zdarzenia

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

Myślę (a może nawet mam nadzieję), że to jeszcze jeden błąd w Chrome.

Wartości offsetLeftoffsetTop są zaokrąglone, co powoduje, że po powiększeniu obrazu są one niedokładne. Problemy z tym związane możesz zobaczyć podczas prezentacji – jeśli użytkownik powiększa i przesuwa mapę powoli, minimapa przeskakuje między nierozwiniętymi pikselami.

Zbyt wolna częstotliwość zdarzeń

Podobnie jak inne zdarzenia resizescroll nie są one wywoływane w każdej klatce, zwłaszcza na urządzeniach mobilnych. Możesz to zobaczyć podczas prezentacji – gdy przybliżysz mapę, minimapa może nie pozostać zablokowana w widocznym obszarze.

Ułatwienia dostępu

prezentacji użyłem visualViewport, aby przeciwdziałać zbliżaniu przez użytkownika. W przypadku tego konkretnego demo ma to sens, ale zanim zrobisz coś, co zastąpi potrzebę użytkownika, aby powiększyć obraz, dobrze się zastanów.

visualViewport może służyć do ułatwiania dostępu. Jeśli na przykład użytkownik przybliża widok, możesz ukryć ozdobne elementy position: fixed, aby nie przeszkadzały użytkownikowi. Pamiętaj jednak, aby nie ukrywać czegoś, co użytkownik chce dokładniej obejrzeć.

Możesz rozważyć przesyłanie danych do usługi analitycznej, gdy użytkownik przybliży widok. Pomoże Ci to zidentyfikować strony, z którymi użytkownicy mają problemy przy domyślnym poziomie powiększenia.

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

To wszystko. visualViewport to przydatny interfejs API, który rozwiązuje problemy ze zgodnością.