Хотя этот вопрос немного старше, я хочу ответить на него, потому что часто вижу сценарии, в которых многие из этих методов используются неправильно.
В общем, все инструменты, о которых вы просили (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
, потому что больше запрашивать изменения макета у браузера не имеет смысла. чаще, чем браузер отображает макет.
Однако событие 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):
/**
* From https://stackoverflow.com/a/44779316
*
* @param {Function} fn Callback function
* @param {Boolean|undefined} [throttle] Optionally throttle callback
* @return {Function} Bound 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;
var that;
var 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
, но дает что-то совершенно другое. Это дает вам несколько периодов простоя внутри кадра. (Обычно это происходит после того, как браузер рассчитал макет и выполнил отрисовку, но до выполнения вертикальной синхронизации еще остается время.) Даже если страница отстает от просмотра пользователями, могут быть некоторые кадры, где браузер холостой ход. Хотя rIC
может дать вам макс. 50 мс. Большую часть времени у вас есть только от 0,5 до 10 мс для выполнения вашей задачи. Из-за того, что в какой момент жизненного цикла фрейма вызываются rIC
обратные вызовы, вам не следует изменять DOM (используйте для этого rAF
).
В конце концов, имеет смысл ограничить прослушиватель scroll
для отложенной загрузки, бесконечной прокрутки и тому подобного с помощью rIC
. Для таких пользовательских интерфейсов вы можете даже увеличить дроссель и добавить перед ним setTimeout
. (так что вы ждете 100 мс, а затем rIC
)
Живые примеры для debounce и дроссель.)
Вот также статья о 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
, всегда используйте пассивные прослушиватели событий, и все будет в порядке.
Если вы используете preventDefault, проверьте, можете ли вы заменить его свойством CSS touch-action
или понизить его, по крайней мере, в вашем дереве DOM (например, нет делегирования событий для этих событий). В случае слушателей wheel
вы можете привязать/отвязать их на mouseenter
/mouseleave
.
В случае любого другого события: нет смысла использовать пассивные прослушиватели событий для повышения производительности. Самое важное примечание: событие scroll
нельзя отменить, поэтому никогда не имеет смысла использовать пассивные прослушиватели событий для scroll
.
В случае представления с бесконечной прокруткой вам не нужно touchmove
, вам нужно только scroll
, поэтому пассивные прослушиватели событий даже не применяются.
Резюме
Чтобы ответить на ваш вопрос
- для ленивой загрузки, бесконечного просмотра используйте комбинацию
setTimeout
+ requestIdleCallback
для ваших прослушивателей событий и используйте rAF
для любой записи макета (мутации DOM).
- для мгновенных эффектов по-прежнему используйте
rAF
для любой записи макета (мутации DOM).
person
alexander farkas
schedule
27.06.2017