Примечание редактора: Бервин, автор этой статьи, является членом рабочей группы W3C по производительности и старшим интерфейсным инженером 360 Navigation. Автор «Введение в Vue.js» (в публикации).
Некоторое время назад я сосредоточил свою энергию на области веб-производительности; важной темой в этой области является то, как сделать веб-страницы более шелковистыми (гладкими).
Чтобы сделать веб-страницу шелковистой, во-первых, нам нужен стандарт, чтобы судить о том, какая веб-страница шелковистая; во-вторых, нам нужно точно измерить данные о производительности веб-страницы; наконец, использовать эффективный метод, чтобы сделать веб-страницу шелковистой. гладкий .
В этой статье мы подробно рассмотрим эти три аспекта.
1. RAIL
В конце концов, какая веб-страница шелковистая? Нам нужен критерий, чтобы помочь в оценке того, являются ли наши веб-страницы шелковистыми или нет.
Команда Chrome разработала ориентированную на пользователя модель производительности под названием RAIL, которая ставит перед инженерами цель: пользователи будут чувствовать себя комфортно, пока веб-страница достигает цели; она разбивает взаимодействие с пользователем на несколько ключевых действий, таких как как: нажмите, загрузите и т. д.;
RAIL разделяет поведение, влияющее на производительность, на четыре области:Response,Animation,Idleа такжеLoad. Правильно, название RAIL происходит от первых букв этих четырех слов, которые легко запомнить.
1.1 отклик
исследование показывает,100msОтветы на операции пользовательского ввода обычно считаются немедленными ответами людей. Дальше связь между действием и реакцией рвется, и люди чувствуют, что ее действие затягивается. Например: когда пользователь нажимает кнопку, если100msЕсли ответ дан внутри пользователя, пользователь почувствует, что ответ очень своевременный, и не почувствует ни малейшего чувства задержки.
1.2 анимация
Частота обновления экрана большинства современных устройств составляет 60 Гц, что соответствует 60 обновлениям экрана в секунду, поэтому, пока веб-анимация работает со скоростью 60 кадров в секунду, мы будем чувствовать, что анимация очень плавная.
F P S Это относится к количеству кадров, передаваемых в секунду, а 60FPS относится к 60 кадрам в секунду; каждый кадр составляет почти 16 миллисекунд после преобразования.(1 秒 = 1000 毫秒) / 60 帧 = 16.66 毫秒/帧
Но обычно браузеру требуется некоторое время, чтобы отобразить содержимое каждого кадра на экране (включая расчет стиля, компоновку, рисование, композитинг и т. д.), поэтому обычно у нас есть только 10 миллисекунд на выполнение кода JS.
1.3 праздный
Для повышения производительности мы обычно полностью используем браузер.период простояСделайте что-нибудь с низким приоритетом. Например, предварительно запросить некоторые данные, которые могут быть использованы в дальнейшем, или сообщить данные анализа в период простоя.
RAIL предусматривает, что задачи, выполняемые в период простоя, не должны превышать50ms, конечно, не только предусмотрено RAIL, рабочей группой W3C по производительности.Longtasks
Стандарт также предусматривает, что задачи, превышающие 50 миллисекунд, являются длинными задачами, тогда50msКак появилось это число?
Браузер является однопоточным, что означает, что основной поток может одновременно обрабатывать только одну задачу.Если одна задача выполняется слишком долго, браузер не может выполнять другие задачи, и пользователь будет чувствовать, что браузер завис, потому что его ввод не получает никакого ответа.
чтобы достичь100msдать ответ в течение50msЭто означает, что даже если в начале задачи пользователя возникает в начале рабочей задачи, браузер по-прежнему имеет 50 мс, чтобы ответить на ввод пользователя, не указывая пользовательскую задержку. Как показано на рисунке 1-1:
Изображение 1-1На самом деле ни бездействующие задачи, ни другие высокоприоритетные задачи не должны выполняться дольше, чем50ms.
1.4 нагрузка
если не в1 секундаЗагрузка веб-страниц и предоставление пользователям возможности просматривать содержимое в середине дня может отвлекать пользователей. Пользователь почувствует, что то, что он собирается делать, прервано, если10 секундНе открыв страницу, пользователи разочаруются, откажутся от того, чем хотят заниматься, и могут больше никогда не вернуться.
1.5 Резюме
С помощью RAIL мы можем судить, является ли наша веб-страница шелковистой или нет. RAIL предусматривает некоторые показатели с точки зрения восприятия пользователем.Пока наша веб-страница соответствует стандартам, наша веб-страница гладкая, как шелк, и пользователи будут чувствовать, что наша веб-страница очень гладкая.
RAIL | ключевой индикатор | Действие пользователя |
---|---|---|
Ответ | менее 100 мс | Нажмите кнопку. |
Анимация | менее 16 мс | Прокручивайте страницы, перетаскивайте пальцем, воспроизводите анимацию и т. д. |
Праздный | Менее 50 мс | Пользователь не взаимодействует со страницей, но основной поток должен быть достаточно гарантирован, чтобы обработать следующий пользовательский ввод. |
Загрузить (Загрузить) | 1000ms | Пользователь загружает страницу и видит содержимое. |
2. Пиксельный конвейер
Пиксельный конвейер — это душа создания шелковистых веб-страниц, и методы, которые мы рассмотрим позже, посвящены именно ему.
Рисунок представляет собой пиксельный конвейер, мы обычно используем JS для изменения какого-либо стиля, затем браузер будет вычислять стиль, затем макет, рисунок и, наконец, отдельные слои объединяются вместе для завершения процесса рендеринга, во время которого каждый шаг Caton может вызвать страница.
Обратите внимание, что не все изменения стиля должны проходить эти пять шагов. Например: если геометрические свойства элемента (ширина, высота и т. д.) изменены в JS, браузеру необходимо пройти все пять шагов. Но если просто изменить цвет текста, макет (Layout) можно пропустить, как показано на следующем рисунке:
За исключением окончательной композиции, первые четыре шага могут быть пропущены в разных сценариях. Например: анимация CSS может пропустить операцию JS, ей не нужно выполнять JS.
css-triggers1Указывает, какие этапы пиксельного конвейера запускаются при изменении различных свойств CSS.
Проще говоря, чем больше шагов проходит пиксельный конвейер, тем больше время рендеринга, и один шаг также может занять много времени по какой-то причине; поэтому независимо от того, много шагов или один шаг занимает много времени, в конец Это приведет к увеличению общего времени рендеринга. Чем дольше общее время, тем выше вероятность превышения показателей, указанных в RAIL.
Простой пример: если рендеринг веб-анимации достигает 60 кадров в секунду, анимация не будет пропускать кадры. Если предположить, что макет и отрисовка конвейера рендеринга занимает 10 мс, а затем добавить время на расчет стиля и композицию, то время, оставшееся JS для обработки анимации, составляет всего несколько миллисекунд.Если выполнение JS превышает несколько миллисекунд, каждый кадр анимации будет стоить несколько миллисекунд, время превысит 16 мс, в это время анимация точно будет терять кадры, и пользователь невооруженным глазом может увидеть явные зависания.
Конечно, даже если можно гарантировать, что общее время на кадр будет меньше 16 мс, все равно нет гарантии, что кадры не будут отброшены. Мы обсудим это подробно позже.
3. Как сделать анимацию более плавной
Чтобы анимация была плавной, она должна достигать 60 кадров в секунду. В этом разделе мы опишем, как сделать анимацию стабильной при 60 кадрах в секунду без потери кадров.
3.1 Измерение производительности анимации с помощью Chrome DevTools
При оценке производительности анимации часто необходимо оценить накладные расходы пиксельного конвейера на покадровой основе; использование Chrome DevTools может помочь нам сделать точные измерения.
В инструментах разработчика Chrome щелкните панель «Производительность» и установите флажок «Снимки экрана». Как показано на рис. 3-1:
Рисунок 3-1 Панель производительности инструментов разработчика ChromeЗатем нажмите кнопку записи и нажмите кнопку остановки после записи, чтобы зафиксировать данные о производительности текущей страницы. Как показано на рис. 3-2:
Рис. 3-2 Сбор данных о производительностиПолученные результаты показаны на рис. 3-3:
Рисунок 3-3 Полученные результаты производительностиМы можем увеличить основной поток, чтобы точно увидеть, какие задачи браузер выполняет в каждом кадре и сколько времени занимает каждая задача. Как показано на рис. 3-4:
Рисунок 3-4 Самая важная часть панели производительностиКак видно из приведенного выше рисунка, задача, выполняемая браузером для рендеринга каждого кадра, такая же, как и для пиксельного конвейера, который мы представили ранее. На приведенном выше рисунке, поскольку это анимация CSS, JS не запущен, но для каждого кадра необходимо рассчитать стиль, макет, рисунок и композицию.
3.2 Как сделать анимацию JS более плавной
Анимация JS использует таймер для непрерывного выполнения JS и завершает анимацию веб-страницы, изменяя стили в JS; если вы хотите обеспечить плавную анимацию, от выполнения JS до окончательного отображения в браузере, общее время на кадр будет самым большим.16ms, чтобы анимация могла достигать 60 кадров в секунду.
Как показано на рис. 3-4, даже если JS не выполняется, браузеру требуется время для расчета стиля, макета, рисунка и т. д., поэтому его необходимо зарезервировать для браузера.достаточно времениДелая эти вещи, теперь время выполнения, оставшееся для JS, составляет всего10ms.
Рис. 3-5. Общее потребление времени каждым кадром должно быть менее 16 мс, а время выполнения JS — менее 10 мс.Как только время выполнения JS превысит10ms, это может привести к тому, что общий пиксельный конвейер этого кадра займет больше, чем16ms, поэтому вы не можете достичь 60 кадров в секунду, но вы думали, что пока время выполнения JS гарантированно будет меньше10msВы уверены, что не будете терять кадры? Наивный~
3.2.1 ИспользованиеrequestAnimationFrame
Даже если вы можете гарантировать, что общее время каждого кадра будет меньше 16 мс, нет никакой гарантии, что не будет потери кадров, в зависимости от того, как запускается JS для выполнения.
предполагается использоватьsetTimeout
илиsetInterval
запустить выполнение JS и изменить стили, чтобы вызвать визуальные изменения; тогда будет такая ситуация, потому чтоsetTimeout
илиsetInterval
Невозможно гарантировать, когда будет выполнена функция обратного вызова, она может выполняться в середине каждого кадра или может выполняться в конце каждого кадра. Таким образом, даже если мы можем гарантировать, что общее время каждого кадра меньше, чем16ms, но если время выполнения приходится на середину или конец каждого кадра, окончательный результат по-прежнему таков, что нет возможности заставить экран меняться каждые 16 мс. Как показано на рис. 3-6:
То есть, даже если мы можем гарантировать, что общее время на кадр меньше, чем16ms, но если вы используете таймер для запуска анимации, анимация все равно будет терять кадры, поскольку время срабатывания таймера не определено. Теперь есть только один API для всей сети, чтобы решить эту проблему, и этоrequestAnimationFrame
, он может обеспечить стабильный запуск функции обратного вызова в начале каждого кадра. Как показано на рис. 3-7:
3.2.2 Избегайте FSL
FSL Это называется принудительной синхронной компоновкой; как упоминалось ранее во введении в пиксельный конвейер, отправка кадра на экран происходит в следующей последовательности:Сначала запустите JS, а затем измените стиль в JS, чтобы вызвать расчет стиля, а затем изменение стиля активирует компоновку, рисование и синтез. Но JavaScript может заставить браузер выполнить макет раньше времени, что называется F S L .
Рисунок 3-8 Принудительная синхронная компоновкаОбычно мы случайно вызываем FSL, см. следующий код:
box.classList.add('big');
const width = box.offsetWidth;
код, добавивclass
Изменил стиль элемента, а затем использовалoffsetWidth
Прочитайте ширину элемента. На первый взгляд все нормально, но этот код вызывает FSL.
Когда JavaScript работает, все значения макета, которые были отображены на предыдущем кадре, известны, мы можем использоватьoffsetWidth
Этот синтаксис получает значение, но браузер стилей, который только что был изменен в этом фрейме, еще не отрендерен, поэтому используйтеoffsetWidth
Такой синтаксис считывает ширину элемента, затем, чтобы браузер сообщил нам значение ширины, он должен сначала вычислить ширину, что требует компоновки. Как показано на рис. 3-8, макет опережает расчет стиля.
Поэтому правильно сначала получить ширину, а затем изменить стиль:
const width = box.offsetWidth;
box.classList.add('big');
Кажется, что даже если FSL запускается, порядок конвейера изменяется, и влияние не кажется таким уж большим. 🤔
Одиночный FSL действительно мало влияет на производительность, но при срабатываниидрожание макета, влияние станет очень большим. См. код ниже:
const container = document.querySelector('.container');
const boxes = document.querySelectorAll('p');
for (var i = 0; i < boxes.length; i++) {
// Read a layout property
const newWidth = container.offsetWidth;
// Then invalidate layouts with writes.
boxes[i].style.width = newWidth + 'px';
}
Функция приведенного выше кода состоит в пакетном изменении ширины элементов N P; в цикле мы сначала получаем ширину элемента контейнера, а затем устанавливаем стиль элемента P. Это приводит к тому, что браузер удаляет макет, а затем вычисляет стили.каждый разИзменение стиля приведет к тому, что только что выполненный макет станет недействительным, потому что мы изменили новый стиль, поэтому, когда следующий цикл считывает ширину, браузер должен снова выполнить макет, и так до конца цикла. Во время цикла браузер продолжает выполнять неверный макет, который называетсядрожание макета; Эта ошибка вызывает очень высокие проблемы с производительностью.
Если мы случайно активируем FSL, инструменты разработчика Chrome выдадут красную строку, как показано на рисунке 3-9:
Рисунок 3-9 Подсказка разработчика FSLПри этом в правом верхнем углу задачи будет красный треугольник, мы можем приблизить задачу для дальнейшего просмотра, как показано на Рисунке 3-10:
Рисунок 3-10 Подсказка разработчика Подробности FSLЕсли вы хотите увидеть демо, вы можете нажать на меня2, Нажмите кнопку в демонстрации, чтобы увеличить ширину тега P.
Чтобы избежать дрожания макета, мы можем поместить код, который считывает ширину элемента, вне цикла. код показывает, как показано ниже:
const container = document.querySelector('.container');
const boxes = document.querySelectorAll('p');
// Read a layout property
const newWidth = container.offsetWidth;
for (var i = 0; i < boxes.length; i++) {
// Then invalidate layouts with writes.
boxes[i].style.width = newWidth + 'px';
}
Если вы хотите увидеть демо, вы можете нажать на меня3, вы можете видеть, что эта демонстрация точно такая же, как и предыдущая, и мы даже не можем сказать, что быстрее невооруженным глазом, потому что элементов DOM меньше, поэтому общее время относительно невелико, но мы может собирать данные о производительности с помощью инструментов разработчика Chrome.
Рисунок 3-11 Время после оптимизацииКак видно на рис. 3-11, общее время этого кадра после оптимизации составило 4,7 мс, тогда как до оптимизации оно составляло 101 мс, как показано на рис. 3-12:
Рисунок 3-12 Время до оптимизацииПосле оптимизации время, затрачиваемое на кадр, меньше, чем до оптимизации.21,7 раза, цифры ошеломляют.
3.3 Как сделать анимацию CSS более плавной
Анимация CSS обычно используется@keyframe
илиtransition
Комбинируйте изменения стиля, чтобы добиться эффекта визуальных изменений. Мы также можем сделать анимацию CSS более плавной, уменьшив количество шагов в конвейере пикселей и время, которое занимает каждый шаг.
Методы оптимизации анимаций CSS, представленные в этом разделе, также применимы к анимациям JS, но методы оптимизации анимаций JS, представленные в предыдущем разделе, неприменимы к анимациям CSS, они являются отношениями сдерживания.
рисоватьОбычно это занимает много времени, и мы можем наблюдать, как рисуется область с помощью инструментов разработчика Chrome. Откройте инструменты разработчика и нажмите клавишу Esc на клавиатуре. В появившейся панели переключитесь на вкладку «рендеринг» и отметьте «Paint flashing». Как показано на рис. 3-13:
Рисунок 3-13 Включение мерцания рисункавключирисовать мерцаниеПосле этого, всякий раз, когда страница рисуется, мы можем видеть мигание зеленого в области рисования на экране. Как показано на рис. 3-14:
Рисунок 3-14 Мерцание области рисованияКак показано на рисунке 3-14, когда мы включаем мерцание рисунка, область рисования будет мигать зеленым,Вы можете нажать на меня, чтобы просмотреть демо4.
Когда мы видим область, которую, по нашему мнению, не следует рисовать, мы должны исследовать ее дальше и убрать эту область.
Как я могу избежать рисования? Ответ: слои.
На самом деле, когда браузер отображает страницу, он может разделить страницу на множество слоев, что чем-то похоже на PhotoShop.Изображение состоит из нескольких слоев в PotoShop, и страница, отображаемая браузером, на самом деле состоит из нескольких слоев. несколько слоев. Как показано на рис. 3-15:
Рисунок 3-15 СлоиВыдвигая элементы, которые постоянно меняются, на отдельный слой, нет необходимости рисовать, браузеру нужно только объединить два слоя вместе,Чтобы просмотреть демо, пожалуйста, сильно нажмите на меня5.
Если вы щелкнете по демонстрационному адресу выше и включите мигание отрисовки, вы обнаружите, что мигание не происходит, потому что браузер не рисует. Если вы посмотрите на панель «Слои», вы увидите что-то вроде этого, как показано на рисунке 3-16:
3-16 слоевКогда мы собираем данные о производительности с помощью панели «Производительность»рисоватьушел. Как показано на рис. 3-17:
Рисунок 3-17 Захват без рисованияЛучший способ создать слой — использоватьwill-change
, но некоторые браузеры, не поддерживающие это свойство, могут использовать 3D-деформацию (transform: translateZ(0)
), чтобы принудительно создать новый слой.
На вкладке «рендеринг» инструментов разработчика Chrome установите флажок «Границы слоя». Вы можете видеть, какие составные слои находятся на странице. Составной слой будет использовать оранжевую рамку, как показано на рис. 3-18:
Рисунок 3-18 показывает составной слой.Чтобы уменьшить отрисовку, можно добавить новые слои, но управление слоями также требует затрат, поэтому во избежание злоупотреблений обычно необходимо проанализировать конкретные ситуации и сделать соответствующий выбор.
Перед моей демонстрацией все измененные элементыleft
Свойство заставляет блок перемещаться, что неизбежно требует операций компоновки, лучше всего использоватьtransform
свойство, это свойство обрабатывается компоновщиком отдельно, поэтому использование этого свойства позволяет избежать компоновки и рисования.
Суммировать
RAIL может помочь нам определить, какие веб-страницы являются безупречными, а инструменты разработчика позволяют более точно фиксировать данные о производительности веб-страниц.
JS-анимация должна гарантировать, что браузеру будет зарезервировано 6 мс времени для обработки пиксельного конвейера, а собственное время выполнения должно быть менее 10 мс, чтобы общая скорость выполнения была менее 16 мс. Но время запуска анимации тоже очень важно, таймер не может запускать анимацию стабильно, поэтому нам нужно использоватьrequestAnimationFrame
Запустите анимацию JS. В то же время мы должны избегать всех FSL, это оказывает огромное влияние на производительность.
CSS-анимации мы можем сделать это, уменьшив область рисования и сделавtransform
В то же время нам нужно хорошо управлять слоями, потому что и рисование, и управление слоями требуют затрат.Обычно нам нужно идти на компромиссы и делать лучший выбор в соответствии с конкретной ситуацией.