Создавайте высокопроизводительные анимации со скоростью 60 кадров в секунду

внешний интерфейс браузер CSS
Создавайте высокопроизводительные анимации со скоростью 60 кадров в секунду

написать впереди

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

текст

Что такое высокопроизводительная анимация?

В качестве меры можно использовать частоту кадров анимации, вообще говоря, картинка работает лучше при частоте кадров 60 кадров в секунду.

fps对比:对我们的眼睛来说30fps感觉流畅,60fps更舒服完美
60fps

Другими словами, каждый кадр должен рендериться за 16,7 мс (16,7 = 1000/60). Поэтому нашим приоритетом является снижение ненужного потребления производительности. Чем больше кадров нужно отрисовать, тем больше задач нужно обработать браузеру, поэтому происходят пропуски кадров, что является камнем преткновения для 60 кадров в секунду.

Если все анимации не рендерятся за 16,7 мс, рассмотрите возможность рендеринга с немного меньшей частотой кадров — 30 кадров в секунду.

Как добиться шелковистой гладкости

Здесь есть два основных определяющих фактора:

Время кадра: когда новый кадр готов

Стоимость (бюджет кадра): сколько времени требуется для рендеринга нового кадра.

Когда начинать рисовать

Обычно мы используемsetTimeout(callback, 1/60)Чтобы добиться рендеринга одного кадра анимации через 16,7 мс. ОднакоsetTimeoutНа самом деле не точно. первый,setTimeoutПоложитесь на частоту обновления встроенных часов браузера Например: IE8 и предыдущий интервал обновления составляет 15,6 мс,setTimeout(callback, 1/60)составляет 16,7 мс, то для запуска требуется два 15,6 мс, что также означает 15,6 x 2 - 16,7 = 14,5 мс с задержкой без причины.

时钟频率
t01457cd049f86cc7ef

Во-вторых, если он может достигать 16,7 мс, он также сталкивается с проблемой асинхронной очереди. Из-за асинхронных отношенийsetTimeoutФункция обратного вызова не выполняется немедленно, а должна быть добавлена ​​в очередь ожидания. Но проблема в том, что если есть новый скрипт синхронизации, который нужно выполнить во время ожидания триггера задержки, скрипт синхронизации не будет поставлен в очередь после обратного вызова таймера, а будет выполнен немедленно.

function runForSeconds(s) {
    var start = +new Date();
    while (start + s * 1000 > (+new Date())) {}
}

document.body.addEventListener("click", function () {
    runForSeconds(10);
}, false);

setTimeout(function () {
    console.log("Done!");
}, 1000 * 3);

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

Основываясь на этих проблемах, мы предлагаем другое решение: requestAnimationFrame(callback)

Метод window.requestAnimationFrame() сообщает браузеру, что вы хотите выполнить анимацию, и просит браузер вызвать указанную функцию для обновления анимации перед следующей перерисовкой. Этот метод принимает в качестве параметра функцию обратного вызова, которая будет вызываться перед перерисовкой браузера.-- MDN

Когда мы вызываем эту функцию, мы говорим ей сделать две вещи:

  1. Нам нужна новая рама;
  2. Когда вы визуализируете новый кадр, вам нужно выполнить функцию обратного вызова, которую я вам передал

По сравнению с setTimeout самым большим преимуществом rAF(requestAnimationFrame) являетсяВ системе определяется время реализации callback-функции.

В частности, система будет активно вызывать функцию обратного вызова в rAF перед каждым отрисовкой.Если частота отрисовки системы составляет 60 Гц, функция обратного вызова будет выполняться каждые 16,7 мс. Если частота отрисовки составляет 75 Гц, то этот интервал становится равным 1000/75=13,3. РС.

Другими словами, темп выполнения rAF соответствует частоте отрисовки системы. Это гарантирует, что функция обратного вызова будет выполняться только один раз в каждом интервале отрисовки экрана (регулирование функции, эта статья не будет вдаваться в подробности, вы можете проверить это, если вам интересно), так что это не приведет к потере кадров, и это не приведет к зависанию анимации.

Кроме того, он может автоматически регулировать частоту. Если работы обратного вызова слишком много для выполнения в одном кадре, она автоматически уменьшится до 30 кадров в секунду. Это ниже, но это лучше, чем пропуск кадров.

В то же время, по сравнению с анимацией, реализованной с помощью setTimeout, когда страница скрыта или свернута, setTimeout по-прежнему выполняет задачи анимации в фоновом режиме, поскольку страница в это время невидима или недоступна, обновлять анимацию бессмысленно, и это также тратит ресурсы процессора. rAF совершенно другой.Когда обработка страницы не активирована, задача рисования экрана страницы также будет приостановлена ​​системой, поэтому rAF, который следует за системой, также прекратит рендеринг.Когда страница активирована, анимация будет начать с последнего раза.Выполнение продолжается с того места, где оно было остановлено, что эффективно снижает нагрузку на ЦП.


На самом деле существует хорошее решение проблемы совместимости rAF, следующее относительно простое:

window.requestAnimFrame = (function(){
 return  window.requestAnimationFrame   || 
   window.webkitRequestAnimationFrame || 
   window.mozRequestAnimationFrame    || 
   window.oRequestAnimationFrame      || 
   window.msRequestAnimationFrame     || 
   function( callback ){
        window.setTimeout(callback, 1000 / 60);
   };
})();

Такой способ записи не учитывает совместимость cancelAnimationFrame, и не все устройства имеют интервал прорисовки 1000/60.

это неплохоpolyfil.

пора рисовать рамку

В целом, rAF решает первую проблему (время отрисовки) выше, а что касается второй проблемы (стоимость отрисовки), то rAF бессилен, и в лучшем случае она решается автоматическим снижением частоты.

Здесь нам нужно оптимизировать с точки зрения браузерного рендеринга, сначала посмотрите на эту картинку:

渲染过程
t01018eff532098fece

Rendering

Когда страница загружается впервые, браузер загружает и анализирует HTML, превращая элементы HTML в «дерево содержимого» узлов DOM. Кроме того, стили также анализируются для создания «дерева рендеринга». Для повышения производительности механизм рендеринга будет выполнять эту работу отдельно, и может даже показаться, что дерево рендеринга генерируется быстрее, чем дерево DOM.

На этом этапе больше всего влияет время рисования, естественно, макет.

// animation loop
function update(timestamp) {
    for(var m = 0; m < movers.length; m++) {
        // DEMO 版本
        //movers[m].style.left = ((Math.sin(movers[m].offsetTop + timestamp/1000)+1) * 500) + 'px';

        // FIXED 版本
        movers[m].style.left = ((Math.sin(m + timestamp/1000)+1) * 500) + 'px';
        }
    rAF(update);
};
rAF(update);

DEMO-версия в приведенном выше примере очень медленная. Причина медлительности в том, что при изменении левого значения каждого объекта будет запрошено значение offsetTop объекта, вызывая перестановку, которая является очень трудоемкой операцией перекомпоновки. .

Обычно мы неосознанно пишем много часто повторяющегося кода макета, например:

var h1 = element1.clientHeight;
element1.style.height = (h1 * 2) + 'px';

var h2 = element2.clientHeight; 
element2.style.height = (h2 * 2) + 'px';

var h3 = element3.clientHeight;
element3.style.height = (h3 * 2) + 'px';

Постоянное чтение и запись в DOM может привести к «принудительной синхронной раскладке», но в процессе развития технологий это превратилось в более яркое слово — «перебивка раскладки» (подробности см. в этой статье).layout thrashing). Браузер будет отслеживать «грязные элементы», сохранять процесс преобразования, когда это необходимо, а затем, после чтения определенного атрибута, разработчик может заставить браузер выполнить расчет заранее, поэтому повторное чтение и запись вызовут перестановку. Так что здесь нам нужно оптимизировать,прочитай перед тем как написатьявляется решением, приведенный выше код можно переписать так:

// Read
var h1 = element1.clientHeight;
var h2 = element2.clientHeight;
var h3 = element3.clientHeight;

// Write
element1.style.height = (h1 * 2) + 'px';
element2.style.height = (h2 * 2) + 'px';
element3.style.height = (h3 * 2) + 'px';

Конечно, это может иметь дело только с некоторыми распространенными ситуациями.Если код разделен или более сложное чтение и запись, за которыми следуют вложенные операции чтения и записи, можно использовать более зрелые решения, такие как fastdom.js. Еще одна маленькая хитрость — использовать rAF для задержки всех операций записи до следующего кадра — тоже хорошее решение.

Paint

После создания макета браузер отображает страницу на экране. Этот шаг аналогичен предыдущему, браузер будет отслеживать грязные элементы и объединять их в большую прямоугольную область. Для каждого кадра происходит только одна перерисовка, чтобы закрасить эту загрязненную область.

Влияние на производительность на этом этапе в основном заключается в перерисовке.

Уменьшить ненужный рисунок

Например, даже если изображение в формате gif невидимо, оно может привести к отрисовке, и для свойства отображения изображения в формате gif следует установить значение none, если оно не требуется. В часто окрашиваемых местах избегайте дорогих стилей. Более дорогие модели:

color,border-style,visibility,background,
text-decoration,background-image,
background-position,background-repeat
outline-color,outline,outline-style
border-radius,outline-width,box-shadow
background-size

Справочный веб-сайт:csstriggers.com/

Уменьшить область рисования

Создайте отдельный слой для элементов, которые вызывают большой диапазон Paint, чтобы уменьшить диапазон Paint.

Вы можете обратиться к этомудемонстрационный сайт, зеленая часть — это область перерисовки:

demo网站截图

Composite

Скомпонуйте все нарисованные элементы. По умолчанию все элементы будут нарисованы в одном слое.Если элементы разделены на разные составные слои, обновление элементов улучшит производительность, а элементы, которые не находятся в одном слое, не будут легко затронуты.

На этом этапе ЦП отрисовывает слой, а ГП генерирует слой. Изменения на композитном слое графического процессора являются наименее дорогостоящими и имеют наименьшую стоимость производительности. Таким образом, оптимизация здесь в основном заключается в том, чтобы внести дорогостоящие изменения в графический процессор, что, как правило, означает включение технологии аппаратного ускорения, которую можно назвать полезной и безвредной, если производительность устройства достаточно активна.

Основными ограничениями здесь являются: пропускная способность между GPC и CPU, а также лимит GPU.


Здесь нам нужно различать работу CPU и GPU:

enter description here
t01918abbc87b4f13ba
ЦП работает больше, а также делится на основной поток и синтетический поток. Основной поток в основном отвечает за:

  1. Вычисление и выполнение Javascript
  2. Расчет стиля CSS
  3. Расчет макета
  4. Отрисовка элементов страницы в растровые изображения (paint), то есть растеризация (Raster)
  5. Передать растровое изображение в поток композитинга

Синтетическая нить в основном отвечает за:

  1. Преобразуйте растровое изображение (GraphicsLayer) втекстураВыгрузка на GPU в виде (пропускная способность между GPC и CPU)
  2. Рассчитать видимую и скоро станет видимой часть страницы (прокрутка)
  3. Обработка CSS-анимации (CSS-анимации работают лучше, потому что на их поток не влияет основной поток.)
  4. Скажите графическому процессору нарисовать растровое изображение на экране.

Графическому процессору нужно только отрисовывать слой, поэтому производительность аппаратного ускорения, несомненно, лучше.


Основные способы включения аппаратного ускорения:

  1. Путем измененияopacityа такжеtransformтриггеры значений
  2. пройти черезtransformСвойство 3D принудительно включает ускорение графического процессора.
  3. will-changeЯвно уведомлять браузер об оптимизации рендеринга одного или нескольких элементов элемента.

После аппаратного ускорения браузер создает для этого элемента отдельный «слой». Когда есть отдельный слой, операция перерисовки этого элемента должна обновляться только сама, не затрагивая другие. Вы можете думать об этом как о частичном обновлении. Так анимация с включенным аппаратным ускорением станет намного плавнее

по умолчанию,transform,opacityЭтот тип свойства css напрямую передается ЦП на ГП для обработки, поскольку ГП может быстро смещать, масштабировать, поворачивать и изменять прозрачность текстуры (текстура: растровое изображение, передаваемое из ЦП в ГП), не переходя через макет основной нити. , процесс покраски. То есть аппаратное ускорение включено.

will-changeЭто новая вещь, она может явно уведомлять браузер об оптимизации рендеринга одного или нескольких элементов элемента.will-changeПолучает различные значения свойств, например одно или несколько свойств CSS (transform, opacity),contentsилиscroll-position. Однако наиболее распространенным значением, вероятно, являетсяauto, это значение указывает, что браузер выполнит оптимизацию по умолчанию:


Хотя GPU хорошо обрабатывает изображения, у него есть и узкое место.

Пропускная способность между процессором и графическим процессором ограничена, и если одновременно обновлять слишком много слоев, это легко достигнет узкого места графического процессора и повлияет на плавность анимации. Поэтому нам нужно контролировать количество слоев и количество красок слоя.

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

Существует определенная степень оптимизации, и мы всегда слышим дискуссии о том, что «слишком много составных слоев мешают рендерингу». Поскольку браузеры сделали все возможное для оптимизации, само решение по оптимизации производительности will-change требует очень много ресурсов. Если браузер продолжает выполнять изменение элемента, это означает, что браузер должен продолжать оптимизировать элемент, а потребление производительности приведет к зависанию страницы.

Слишком много составных слоев ухудшают производительность страницы. Это обычное явление для мобильных устройств.


Избегайте случайно созданных слоев

Элементы с z-индексом выше, чем Layer, также будут генерировать отдельный слой.

демо и страница с описанием

резюме

Есть два основных фактора шелковистой гладкости:

Хронометраж (хронометраж кадра):

  • rAF

Стоимость (бюджет кадра):

  • Избегайте компоновки: читайте перед записью

  • Рисуйте как можно меньше: обратите внимание на использование стилей

  • Правильное аппаратное ускорение