Ответ 1
Хотя этот вопрос немного старше, я хочу ответить на него, потому что я часто вижу сценарии, в которых многие из этих методов используются неправильно.
В общем, все ваши запрашиваемые инструменты (rAF
, rIC
и пассивные слушатели) являются отличными инструментами и скоро не исчезнут. Но вы должны знать, зачем их использовать.
Прежде чем я начну: в случае, если вы генерируете прокручиваемые синхронизированные/прокручиваемые связанные эффекты, такие как параллакс-эффекты/липкие элементы, регулирование с помощью rIC
, setTimeout
не имеет смысла, потому что вы хотите немедленно реагировать.
requestAnimationFrame
rAF
дает вам точку в жизненном цикле фрейма непосредственно перед тем, как браузер хочет рассчитать новый стиль и макет документа. Вот почему он идеально подходит для анимации. Во-первых, он не будет вызываться чаще или реже, чем браузер рассчитывает макет (правильная частота). Во-вторых, он вызывается прямо перед тем, как браузер рассчитает макет (правильное время). На самом деле использование rAF
для любых изменений макета (DOM или CSSOM) имеет большой смысл. rAF
синхронизируется с V-SYNC как любой другой материал, связанный с отображением макета в браузере.
используя rAF
для газа/дебата
Пример по умолчанию Пола Льюиса выглядит следующим образом:
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);
Этот шаблон очень часто используется/копируется, хотя на практике он мало что дает, пока не имеет смысла. (И я спрашиваю себя, почему ни один разработчик не видит эту очевидную проблему.) В общем, теоретически имеет смысл rAF
все на хотя бы rAF
, потому что не имеет смысла запрашивать изменения макета из браузера больше часто, чем браузер отображает макет.
Однако событие scroll
срабатывает каждый раз, когда браузер отображает изменение положения прокрутки. Это означает, что событие scroll
синхронизировано с отображением страницы. Буквально то же самое, что дает вам rAF
. Это означает, что не имеет никакого смысла что-то задушить чем-то, что уже задушено одной и той же вещью в определении.
На практике вы можете проверить то, что я только что сказал, добавив console.log
и проверить, как часто этот шаблон "предотвращает множественные обратные вызовы rAF" (ответ - нет, в противном случае это будет ошибка браузера).
// Prevent multiple rAF callbacks.
if (scheduledAnimationFrame){
console.log('prevented rAF callback');
return;
}
Как вы увидите, этот код никогда не выполняется, это просто мертвый код.
Но есть очень похожая модель, которая имеет смысл по другой причине. Это выглядит так:
//declare box, element, pos
function writeLayout(){
element.classList.add('is-foo');
}
window.addEventListener('scroll', ()=> {
box = element.getBoundingClientRect();
if(box.top > pos){
requestAnimationFrame(writeLayout);
}
});
С этим шаблоном вы можете успешно уменьшить или даже убрать разметку макета. Идея проста: внутри вашего слушателя прокрутки вы читаете макет и решаете, нужно ли вам модифицировать DOM, а затем вызываете функцию, которая модифицирует DOM с помощью rAF. Почему это полезно? rAF
гарантирует, что вы переместите свой макет как недействительный (в конце кадра). Это означает, что любой другой код, который вызывается внутри того же фрейма, работает с допустимым макетом и может работать с суперскоростными методами чтения макета.
Этот шаблон на самом деле настолько велик, что я бы предложил следующий вспомогательный метод (написанный на 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
По API похож на rAF
но дает что-то совершенно другое. Это дает вам несколько периодов простоя внутри кадра. (Обычно точка после того, как браузер вычислил макет и выполнил рисование, но до v-синхронизации все еще остается некоторое время.) Даже если страница запаздывает с точки зрения пользователей, могут быть некоторые кадры, в которых браузер находится холостой ход. Хотя rIC
может дать вам макс. 50мс. Большую часть времени у вас есть только от 0,5 до 10 мс для выполнения вашей задачи. В связи с тем, что в какой-то момент в жизненном цикле кадра rIC
обратные вызовы rIC
, вам не следует изменять DOM (используйте для этого rAF
).
В конце концов, имеет смысл задушить слушателя scroll
для ленивой загрузки, бесконечной прокрутки и тому подобного, используя rIC
. Для таких типов пользовательских интерфейсов вы можете даже увеличить setTimeout
и добавить setTimeout
перед ним. (таким образом, вы ждете 100 мс, а затем - rIC
) (Пример жизни для дебаза и дросселя.)
Вот также статья о rAF
, которая включает две диаграммы, которые могут помочь понять различные моменты внутри "жизненного цикла фрейма".
Пассивный слушатель событий
Для улучшения производительности прокрутки были изобретены пассивные слушатели событий. Современные браузеры перемещают прокрутку страниц (отрисовку прокрутки) из основного потока в поток композиции. (см. https://hacks.mozilla.org/2016/02/smoother-scrolling-in-firefox-46-with-apz/)
Но есть события, которые производят прокрутку, которая может быть предотвращена скриптом (что происходит в главном потоке и, следовательно, может вернуть улучшение производительности).
Это означает, что как только один из этих слушателей событий связан, браузер должен дождаться, пока эти слушатели будут выполнены, прежде чем браузер сможет вычислить прокрутку. Эти события в основном touchstart
, touchmove
, touchend
, wheel
и в теории до некоторой степени keypress
и keydown
. scroll
событие само по себе не один из этих событий. Событие scroll
не имеет действия по умолчанию, которое может быть предотвращено сценарием.
Это означает, что если вы не используете preventDefault
в вашем touchstart
, touchmove
, touchend
и/или wheel
, всегда используйте пассивные прослушиватели событий, и у вас все будет хорошо.
Если вы используете protectDefault, отметьте, можете ли вы заменить его свойством CSS touch-action
или уменьшить его, по крайней мере, в своем дереве DOM (например, без делегирования событий для этих событий). В случае слушателей wheel
вы можете связать/открепить их на mouseenter
/mouseleave
.
В случае любого другого события: не имеет смысла использовать пассивные прослушиватели событий для улучшения производительности. Самое главное, чтобы отметить: событие scroll
не может быть отменено, и поэтому никогда не имеет смысла использовать пассивные прослушиватели событий для scroll
.
В случае бесконечного просмотра с прокруткой вам не нужно touchmove
, вам нужна только scroll
, поэтому пассивные слушатели событий даже не применяются.
Продолжить
Ответить на ваш вопрос
- для отложенной загрузки в бесконечном представлении используйте комбинацию
setTimeout
+requestIdleCallback
для прослушивателей событий и используйтеrAF
для любой записи макета (мутации DOM). - для мгновенных эффектов все еще используйте
rAF
для любой записи макета (мутации DOM).