Это последний пост из серии 4-х постов, посвященной заглядыванию внутрь Chrome, и исследующей, как он обрабатывает наш код для отображения веб-сайта. В предыдущем посте мы рассмотрели *рендер-процесс (renderer process) и узнали о *композ-потоке (compositor thread). В этом посте мы рассмотрим, как *композ-поток обеспечивает плавное взаимодействие при вводе данных пользователем.  Часть 1
Часть 2
Часть 3
Часть 4 (текущая) Когда вы слышите "события ввода", вы можете подумать только о вводе в текстовое поле или щелчке мышью, но с точки зрения браузера, ввод означает любой жест пользователя. Прокрутка колеса мыши — это событие ввода, и нажатие или наведение мыши также является событием ввода. Когда происходит пользовательский жест, например, прикосновение к экрану, *браузер-процесс — является тем процессом, который получает этот жест первым. Однако *браузер-процесс знает только о том, где этот жест произошел, поскольку содержимое внутри вкладки обрабатывается *рендер-процессом. Так что *браузер-процесс посылает в *рендер-процесс тип события (например touchstart) и его координаты. *Рендер-процесс соответствующим образом обрабатывает событие, находя цель события и запуская слушателей события, которые прикреплены к нему.  Рисунок 1: Событие ввода проходит через *браузер-процесс в *рендер-процесс В предыдущем посте мы рассмотрели, как *композ-поток может делать прокрутку плавной, создавая растровые слои. Если к странице не подключены слушатели входных событий, *композ-поток может создать новую кадр-композицию, полностью независимую от основной. Но что делать, если к странице прикреплены слушатели событий? Как *композ-поток узнает, нужно ли обрабатывать событие? 
Рисунок 2: Viewport наводится на слои страницы Поскольку выполнение JavaScript является задачей главного потока, при компоновке страницы *композ-поток отмечает область страницы, к которой прикреплены обработчики событий как "Non-Fast Scrollable Region" (*нбс-регион). Обладая этой информацией, *композ-поток может гарантировать, что входное событие будет отправлено в главный поток, если событие произойдет в этом регионе. Если входное событие приходит из-за пределов этого региона, то *композ-поток продолжает композицию нового кадра, не дожидаясь главного потока.  Рисунок 3: Схема описывающая ввод в *нбс-регион Общей моделью обработки событий в веб-разработке является делегирование событий. Так как события всплывают, вы можете прикрепить одного обработчика событий к самому верхнему элементу и делегировать задачи в зависимости от цели события. Возможно, вы видели или писали код, подобный приведенному ниже. Так как для всех элементов необходимо написать только один обработчик события, использование этого шаблона делегирования событий привлекательно. Однако, если посмотреть на этот код с точки зрения браузера, то теперь вся страница помечена как *нбс-регион. Это означает, что даже если ваше приложение не интересуют данные вводимые в определенных частях страницы, *композ-поток должен взаимодействовать с главным потоком и ждать его каждый раз, когда приходит входящее событие. Таким образом, способность композитора к плавной прокрутке оказывается подорванной.  Рисунок 4: Схема описывающая ввод в *нбс-регион покрывающий всю страницу Для того, чтобы это не произошло, вы можете передать опцию passive:true в вашем слушателе событий. Это подскажет браузеру, что вы все еще хотите слушать событие в главном потоке, а *композ-поток сможет продолжить композировать новый кадр. Представьте, что на странице есть поле, ограничивающее направление прокрутки только горизонталью. Использование опции passive: true в событии курсора означает, что прокрутка страницы может быть гладкой, но вертикальная прокрутка может начаться к тому времени, когда вы хотите выполнить preventDefault для ограничения направления прокрутки. Вы можете проверить это, используя метод event.cancelable. 
Рисунок 5: Веб-страница с частью страницы у которой скролл только горизонтальный Альтернативно, вы можете использовать CSS-правило, такое как touch-action, чтобы полностью устранить обработчик события. Когда *композ-поток посылает входное событие в главный поток, первым делом выполняется проверка попадания в цель события. Проверка попадания использует *записи-отрисовки (paint records), которые были сгенерированы в процессе рендеринга, чтобы выяснить, что находится под координатами точки, в которой произошло событие.  Рисунок 6: Главный поток ищет *записи-отрисовки запрашивая что нарисовано в точке x.y В предыдущем посте мы обсуждали, что наш типичный дисплей обновляет экран 60 раз в секунду и что нам нужно идти в ногу с частотой кадров для плавной анимации. При вводе данных типичное устройство с сенсорным экраном передает события 60-120 раз в секунду, а типичная мышь передает события 100 раз в секунду. Событие ввода имеет более высокую точность, чем может обновить наш экран. Если непрерывное событие, подобное сенсорному движению, посылается в основной поток 120 раз в секунду, то оно может вызвать избыточное количество проверок на попадание и выполнение JavaScript по сравнению с тем, как не быстро может обновляться экран.  Рисунок 7: События забивают временнУю ленту кадров что приводит к дрожанию страницы Для минимизации излишних вызовов главного потока, Chrome объединяет (coalesces, *коалесирует) непрерывные события (такие как wheel, mousewheel, mousemove, pointermove, touchmove) и задерживает диспетчеризацию до момента, непосредственно предшествующего следующему requestAnimationFrame.  Рисунок 8: Та же лента но события *коалесируются и придерживаются Любые дискретные события, такие как нажатие keydown, keyup, mouseup, mousedown, touchstart и touchchend отправляются немедленно. Для большинства веб-приложений, *коалесирования событий должно быть достаточно, чтобы обеспечить хороший пользовательский опыт. Однако, если вы строите такие вещи, как приложение для рисования и прокладываете путь на основе координат touchmove, вы можете потерять промежуточные координаты для отрисовки гладкой линии. В этом случае вы можете использовать метод getCoalescedEvents в событии курсора, чтобы получить информацию об этих *коалесированных событиях.  Рисунок 9: Плавный путь от жеста касания слева, рваный из-за *коалесирований пути справа В этой серии постов мы рассмотрели внутреннюю работу веб-браузера. Если вы никогда не задумывались о том, почему DevTools рекомендует добавлять {passive: true} в обработчик событий или зачем писать атрибут async в теге сценария, я надеюсь, что эта серия прольёт некоторый свет на то, почему браузеру нужна эта информация для обеспечения более быстрой и плавной работы с веб. Если вы хотите, чтобы ваш код был удобен для браузера, но не знаете, с чего начать, то рекомендую Lighthouse — это инструмент, который проводит аудит любого сайта и даёт вам отчет о том, что делается правильно и что нуждается в улучшении. Читая список аудитов, вы также получаете представление о том, о чем беспокоится браузер. Настройки производительности могут отличаться для разных сайтов, поэтому очень важно, чтобы вы измерили производительность вашего сайта и решили, что лучше всего подходит для него. Команда Chrome DevTools имеет несколько руководств по оценке производительности вашего сайта. Если вы хотите сделать дополнительный шаг, Feature Policy — это новая функция веб-платформы, которая может быть ограждением для вас, когда вы строите свой проект. Включение Feature Policy гарантирует определенное поведение вашего приложения и предотвращает ошибки. Например, если вы хотите гарантировать, что ваше приложение никогда не будет блокировать парсинг, вы можете запустить ваше приложение на основе политики синхронных сценариев. При включённом sync-script: 'none', блокирование JavaScript-ом парсера будет отключено. Это не позволит ни одному вашему коду заблокировать парасер, а браузеру будет не нужно беспокоиться о том, чтобы ставить парсер на паузу. Когда я начинала создавать сайты, меня волновало только то, как я буду писать свой код и что поможет мне быть более продуктивной. Это важно, но мы также должны думать о том, как браузер принимает код, который мы пишем. Современные браузеры вкладывали и продолжают вкладывать средства в то, чтобы предоставить пользователям более качественный веб-интерфейс. Будьте любезны с браузером, организовывая наш код, и тем самым также, улучшая свой пользовательский опыт. Надеюсь, Вы присоединитесь ко мне в стремлении быть добрее к браузерам!  Огромное спасибо всем, кто рецензировал ранние проекты этой серии, включая (но не ограничиваясь): Alex Russell, Paul Irish, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasuda, Nasko Oskov, и Charlie Reis. Вам понравилась эта серия? Если у вас есть вопросы или предложения для будущих постов, я бы хотела услышать их от вас в разделе комментариев ниже или у меня в твиттере — @kosamari. Часть 1
Часть 2
Часть 3
Часть 4 (текущая) Web-разработчик, Android-разработчик