Выровненные входные события

Дэйв Тапуска
Dave Tapuska

ТЛ;ДР

  • Chrome 60 уменьшает задержки за счет снижения частоты событий, тем самым улучшая согласованность синхронизации кадров.
  • Метод getCoalescedEvents() , представленный в Chrome 58, предоставляет тот же объем информации о событиях, что и раньше.

Обеспечение плавного взаимодействия с пользователем важно для Интернета. Время между получением события ввода и моментом фактического обновления визуальных элементов важно, и, как правило, важно выполнять меньше работы. За последние несколько выпусков Chrome мы снизили задержку ввода на этих устройствах.

В целях плавности и производительности в Chrome 60 мы вносим изменение, благодаря которому эти события происходят с меньшей частотой, одновременно увеличивая детализацию предоставляемой информации. Подобно тому, как был выпущен Jelly Bean и появился Choreographer , который выравнивает ввод на Android, мы добавляем в Интернет ввод с выравниванием по кадру на всех платформах.

Но иногда вам нужно больше событий. Итак, в Chrome 58 мы реализовали метод getCoalescedEvents() , который позволяет вашему приложению получать полный путь указателя, даже если оно получает меньше событий.

Давайте сначала поговорим о частоте событий.

Снижение частоты событий

Давайте разберемся с некоторыми основами: сенсорные экраны передают входной сигнал с частотой 60–120 Гц, а мыши обычно передают входной сигнал с частотой 100 Гц (но могут быть и до 2000 Гц). Однако типичная частота обновления монитора составляет 60 Гц. Так что же это на самом деле означает? Это означает, что мы получаем ввод с более высокой скоростью, чем на самом деле обновляем отображение. Итак, давайте посмотрим на график производительности из инструментов разработчика для простого приложения для рисования на холсте.

На рисунке ниже, когда отключен вход с выравниванием requestAnimationFrame() , вы можете увидеть несколько блоков обработки на кадр с непостоянным временем кадра. Маленькие желтые блоки обозначают тестирование на попадание для таких вещей, как цель события DOM, отправка события, запуск JavaScript, обновление узла, находящегося при наведении, и, возможно, перерасчет макета и стилей.

Временная шкала производительности, показывающая несогласованную синхронизацию кадров

Так почему же мы выполняем дополнительную работу, которая не приводит к визуальным обновлениям? В идеале мы не хотим выполнять какую-либо работу, которая в конечном итоге не принесет пользы пользователю. Начиная с Chrome 60, входной конвейер будет задерживать отправку непрерывных событий ( wheel , mousewheel , touchmove , pointermove , mousemove ) и отправлять их непосредственно перед выполнением обратного вызова requestAnimationFrame() . На рисунке ниже (с включенной функцией) вы видите более стабильное время кадра и меньшее время обработки событий.

Мы провели эксперимент с включенной этой функцией на каналах Canary и Dev и обнаружили, что мы выполняем на 35% меньше проверок на попадание, что позволяет основному потоку быть готовым к запуску чаще.

Важное замечание, о котором веб-разработчикам следует знать: любое возникающее дискретное событие (например, keydown , keyup , mouseup , mousedown , touchstart , touchend ) будет отправлено сразу же вместе с любыми ожидающими событиями, сохраняя относительный порядок. Если эта функция включена, большая часть работы упрощается в обычном потоке цикла событий , обеспечивая согласованный интервал ввода. Это приводит к тому, что непрерывные события встроены в события scroll и resize , которые уже встроены в поток цикла событий в Chrome.

Временная шкала производительности, показывающая относительно постоянную синхронизацию кадров.

Мы обнаружили, что подавляющее большинство приложений, использующих такие события, не используют более высокую частоту. Android уже несколько лет выравнивает события, поэтому нет ничего нового, но сайты могут иметь менее детализированные события на настольных платформах. Всегда существовала проблема с нестабильными основными потоками, приводившими к сбоям в плавности ввода, а это означало, что вы могли видеть скачки позиций всякий раз, когда приложение выполняет работу, что делало невозможным узнать, как указатель попал из одного места в другое.

Метод getCoalescedEvents()

Как я уже сказал, существуют редкие сценарии, когда приложение предпочитает знать полный путь указателя. Поэтому, чтобы исправить ситуацию, когда вы видите большие скачки и уменьшенную частоту событий, в Chrome 58 мы запустили расширение для событий указателя под названием getCoalescedEvents() . А ниже приведен пример того, как джанк в основном потоке скрывается от приложения, если вы используете этот API.

Сравнение стандартных и объединенных событий.

Вместо получения одного события вы можете получить доступ к массиву исторических событий, вызвавших это событие. Android , iOS и Windows имеют очень похожие API в своих собственных SDK, и мы предоставляем аналогичный API в Интернете.

Обычно приложение для рисования может нарисовать точку, просматривая смещения события:

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

Этот код можно легко изменить, чтобы использовать массив событий:

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

Обратите внимание, что не все свойства объединенных событий заполняются. Поскольку объединенные события на самом деле не отправляются, а просто используются, они не проверяются на попадание. Некоторые поля, такие как currentTarget и eventPhase , будут иметь значения по умолчанию. Вызов методов, связанных с диспетчеризацией, таких как stopPropagation() или preventDefault() не окажет никакого влияния на родительское событие.