zdarzenia przewijania: requestAnimationFrame VS requestIdleCallback VS passive event listeners

Jak wiemy, często zaleca się debounce scroll listeners, aby UX był lepszy, gdy użytkownik przewija.

Jednak często znajdowałem biblioteki i artykuły, w których wpływowi ludzie, tacy jak Paul Lewis, zalecają używanie requestAnimationFrame. Jednak w miarę szybkiego postępu platformy internetowej może być możliwe, że niektóre porady zostaną z czasem wycofane.

Widzę, że istnieją bardzo różne przypadki użycia do obsługi zdarzeń przewijania, takie jak budowanie paralaksy strona internetowa, czyli obsługa nieskończonego przewijania i paginacji.

Widzę 3 główne narzędzia, które mogą zrobić różnicę w term UX:

Więc, chciałbym wiedzieć, na usecase (mam tylko 2, ale można wymyślić inne), jakiego narzędzia powinienem teraz użyć, aby mieć bardzo dobre doświadczenie przewijania?

Aby być bardziej precyzyjnym, mój główny pytanie byłoby bardziej związane z nieskończonym przewijaniem widoków i paginacją (które zazwyczaj nie muszą wywoływać animacji wizualnych, ale chcemy dobrego przewijania), czy lepiej zastąpić requestAnimationFrame kombinacją requestIdleCallback + pasywny mechanizm obsługi zdarzeń przewijania ? Zastanawiam się również, kiedy sensowne jest użycie requestIdleCallback do wywołania API lub obsługi odpowiedzi API, aby umożliwić przewijanie lepiej, czy jest to coś, co przeglądarka może już obsługiwać za nas?

Author: Drewness, 2017-01-19

1 answers

Chociaż to pytanie jest trochę starsze, chcę na nie odpowiedzieć, ponieważ często widzę skrypty, w których wiele z tych technik jest nadużywanych.

Ogólnie rzecz biorąc, wszystkie narzędzia, o które prosisz (rAF, rIC i pasywnych słuchaczy) są świetnymi narzędziami i nie znikną szybko. Ale musisz wiedzieć, dlaczego ich używać.

Zanim zacznę: w przypadku wygenerowania scroll synced / scroll linked effects np. paralaksa effects/sticky elements, Dławienie za pomocą rIC, setTimeout to nie ma sensu, bo chcesz natychmiast zareagować.

requestAnimationFrame

rAF daje punkt wewnątrz cyklu życia ramki tuż przed przeglądarka chce obliczyć nowy styl i układ dokumentu. Dlatego jest idealny do wykorzystania do animacji. Najpierw nie będzie wywoływany częściej lub rzadziej niż przeglądarka oblicza układ (odpowiednią częstotliwość). Po drugie jest wywoływany tuż przed obliczeniem układu przez przeglądarkę(odpowiedni czas). W rzeczywistości używanie rAF do wszelkich zmian układu (DOM lub CSSOM zmiany) ma wiele sensu. rAF jest synchronizowany z V-SYNC jak każdy inny układ renderowania związane rzeczy w przeglądarce.

Użycie rAF dla przepustnicy / debounce

Domyślny przykład Paula Lewisa wygląda tak:

var scheduledAnimationFrame;
function readAndUpdatePage(){
  console.log('read and update');
  scheduledAnimationFrame = false;
}

function onScroll (evt) {

  // Store the scroll value for laterz.
  lastScrollY = window.scrollY;

  // Prevent multiple rAF callbacks.
  if (scheduledAnimationFrame){
    return;
  }

  scheduledAnimationFrame = true;
  requestAnimationFrame(readAndUpdatePage);
}

window.addEventListener('scroll', onScroll);

Ten wzór jest bardzo często używany/kopiowany, chociaż w praktyce nie ma to większego sensu. (I zadaję sobie pytanie, dlaczego żaden deweloper nie widzi tego oczywistego problemu.) Ogólnie rzecz biorąc, teoretycznie ma to sens, aby wszystko przynajmniej rAF, ponieważ nie ma sensu żądać zmian układu z przeglądarki częściej niż przeglądarka renderuje układ.

Jednak Zdarzenie scrolljest wyzwalane za każdym razem, gdy przeglądarka renderuje zmianę pozycji przewijania. Oznacza to, że zdarzenie scroll jest zsynchronizowane z renderowaniem strony. Dosłownie to samo, co daje wam. Oznacza to, że nie ma sensu Dławić czegoś przez coś, co jest już dławione przez dokładnie to samo rzecz według definicji.

W praktyce możesz sprawdzić to, co powiedziałem dodając console.log i sprawdzić, jak często ten wzór "zapobiega wielokrotnym wywołaniom rAF" (odpowiedź brzmi: brak, w przeciwnym razie byłby to błąd przeglądarki).

  // Prevent multiple rAF callbacks.
  if (scheduledAnimationFrame){
    console.log('prevented rAF callback');
    return;
  }

Jak zobaczysz ten kod nigdy nie jest wykonywany, jest to po prostu martwy kod.

Ale jest bardzo podobny wzór, który ma sens z innego powodu. Wygląda to tak:

//declare box, element, pos
function writeLayout(){
    element.classList.add('is-foo');
}

window.addEventListener('scroll', ()=> {
    box = element.getBoundingClientRect();

    if(box.top > pos){
        requestAnimationFrame(writeLayout);
    }
});

Za pomocą tego wzoru można skutecznie zmniejszyć lub nawet usunąć rozbijanie układu. Pomysł jest prosty: w Twoim czytniku przewijania czytasz layout i decydujesz, że musisz zmodyfikować DOM, a następnie wywołujesz funkcję, która modyfikuje DOM za pomocą rAF. Dlaczego jest to pomocne? rAF upewnia się, że przesuniesz unieważnienie układu (na końcu ramki). Oznacza to, że każdy inny kod wywołany wewnątrz tej samej ramki działa na prawidłowym układzie i może działać za pomocą super szybkich metod odczytu układu.

Ten wzór jest w rzeczywistości tak wielki, że sugeruje następującą metodę pomocniczą (napisaną w ES5):

/**
 * @param fn {Function}
 * @param [throttle] {Boolean|undefined}
 * @return {Function}
 *
 * @example
 * //generate rAFed function
 * jQuery.fn.addClassRaf = bindRaf(jQuery.fn.addClass);
 *
 * //use rAFed function
 * $('div').addClassRaf('is-stuck');
 */
function bindRaf(fn, throttle){
    var isRunning, that, args;

    var run = function(){
        isRunning = false;
        fn.apply(that, args);
    };

    return function(){
        that = this;
        args = arguments;

        if(isRunning && throttle){return;}

        isRunning = true;
        requestAnimationFrame(run);
    };
}

requestIdleCallback

Jest z API podobny do rAF, ale daje coś zupełnie innego. Daje to pewne okresy bezczynności wewnątrz ramy. (Zwykle jest to punkt po tym, jak przeglądarka obliczyła layout i zrobiła paint, ale pozostało jeszcze trochę czasu, zanim nastąpi synchronizacja v-sync.) Nawet jeśli strona jest opóźniona w widoku Użytkowników, mogą istnieć pewne ramki, w których przeglądarka jest bezczynna. Chociaż rIC może dać ci max. 50ms. przez większość czasu masz tylko od 0,5 do 10ms, aby spełnić swoje zadanie. Ze względu na fakt, w którym momencie w cyklu życia ramki rIC wywołania zwrotne są wywoływane, nie należy zmieniać DOM (użyj do tego rAF).

Na końcu ma sens Dławić słuchacza scroll do leniuchowania, nieskończonego przewijania i tego typu używania rIC. W przypadku tego rodzaju interfejsów użytkownika można nawet przycisnąć więcej i dodać setTimeout przed nim. (więc robisz 100ms czekać, a następnie rIC) (przykład życia dla debounce i throttle .)

Oto również artykuł o rAF, obejmuje to dwa diagramy, które mogą pomóc zrozumieć różne punkty wewnątrz "cyklu życia ramki".

Pasywny słuchacz zdarzeń

Pasywne słuchacze zdarzeń zostały wymyślone, aby poprawić wydajność przewijania. Nowoczesne przeglądarki przeniosły przewijanie strony (renderowanie przewijania) z głównego wątku do wątku kompozycji. (patrz https://hacks.mozilla.org/2016/02/smoother-scrolling-in-firefox-46-with-apz/)

Ale są zdarzenia, które powodują przewijanie, które można zapobiec przez skrypt (co dzieje się w głównym wątku, a tym samym może przywrócić poprawę wydajności).

Co oznacza, że gdy tylko jedno z tych zdarzeń zostanie połączone, przeglądarka musi poczekać, aż zostaną one wykonane, zanim przeglądarka będzie mogła obliczyć przewijanie. Wydarzenia te to głównie touchstart, touchmove, touchend, wheel i w teorii do pewnego stopnia keypress i keydown. Samo zdarzenie scroll jest , a nie jednym z tych zdarzeń. Zdarzenie scroll nie ma domyślnej akcji, której można zapobiec skryptem.

Oznacza to, że jeśli nie używasz preventDefault w swoim touchstart, touchmove, touchend i / lub wheel, Zawsze używaj pasywnych słuchaczy zdarzeń i powinno być dobrze.

Jeśli używasz preventDefault, sprawdź czy możesz go zastąpić właściwością CSS touch-action lub obniżyć ją przynajmniej w swoim drzewie DOM (dla przykład brak delegacji zdarzeń dla tych zdarzeń). W przypadku wheel słuchaczy możesz być w stanie powiązać/rozłączyć je na mouseenter/mouseleave.

W przypadku jakiegokolwiek innego zdarzenia: nie ma sensu używać pasywnych słuchaczy zdarzeń do poprawy wydajności. Najważniejsze, aby pamiętać: Zdarzenie scroll nie może zostać odwołane, dlatego } nigdy nie ma sensu używać pasywnych słuchaczy zdarzeń dla scroll.

W przypadku nieskończonego widoku przewijania nie potrzebujesz touchmove, tylko need scroll, więc pasywne słuchacze zdarzeń nawet nie mają zastosowania.

CV

Aby odpowiedzieć na twoje pytanie

  • do lazyloadingu, nieskończony widok użyj kombinacji setTimeout + requestIdleCallback dla swoich słuchaczy zdarzeń i użyj rAF do dowolnego zapisu układu (DOM).
  • do natychmiastowych efektów nadal używaj rAF do dowolnego zapisu układu (DOM).
 49
Author: alexander farkas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-24 11:51:45