Анимации на веб-страницах сегодня являются неотъемлемой частью пользовательского интерфейса. Они делают веб-приложения более эффективными и приятными для взаимодействия. В этой статье мы рассмотрим два основных JavaScript API для разработки анимаций в браузере – requestAnimationFrame и requestIdleCallback. Узнаем, чем они отличаются и для каких случаев подходит каждый из них. Также мы изучим примеры использования этих интерфейсов для создания плавных визуализаций элементов на странице.
Плавная анимация с помощью requestAnimationFrame
На первый взгляд, создание анимации с помощью JavaScript может показаться простой задачей. Однако при ее реализации иногда возникает проблема синхронизации частоты смены кадров в разных браузерах и на разных компьютерах.
Интерфейс requestAnimationFrame позволяет эффективно решить эту проблему. Он синхронизирует частоту обновления кадров анимации с частотой обновления экрана, которая обычно составляет 60 кадров в секунду. Это дает значительное преимущество по сравнению с графическими эффектами на CSS, где приходится использовать простые повторяющиеся переходы. Благодаря requestAnimationFrame можно создавать сложные сценарии анимации, учитывающие временные функции и другую логику.
В целом, этот интерфейс позволяет легко реализовывать плавные и эффектные переходы на JavaScript. Далее мы подробнее разберем его использование.
Использование setInterval JS для анимации
В JavaScript еще с самых ранних версий присутствует функция setInterval. Разработчики до сих пор активно используют ее для создания анимаций, несмотря на то, что появилось множество новых технологий. Этот инструмент позволяет регулярно выполнять определенный код с заданным интервалом.
Основные характеристики функции setInterval:
- Принимает два аргумента: callback-функцию, которая будет выполняться, и интервал между вызовами (в миллисекундах).
- Возвращает идентификатор таймера, по которому можно остановить выполнение через clearInterval.
- Callback-функция вызывается асинхронно по таймеру через указанный интервал.
- Интервал не гарантирует точного времени вызова, это зависит от загруженности процессора.
Функция setInterval часто используется для создания простых анимаций, периодических запросов на сервер и других регулярных задач. В целом, это простой и удобный способ реализовать повторяющиеся действия в JavaScript Animation с заданной периодичностью. И это будет работать независимо от устройства и браузера.
Рассмотрим простой пример:
let timer = setInterval(function() { if (animation complete) clearInterval(timer); else increase style.left by 2px }, 20); // изменять на 2px каждые 20ms, это около 50 кадров в секунду
Здесь setInterval вызывает функцию каждые 20 миллисекунд (примерно 50 кадров в секунду). На каждой итерации проверяется завершение анимации и увеличивается смещение элемента style.left на 2px.
Для плавной анимации обычно достаточно 24 кадров в секунду. Поэтому интервал 20 мс является оптимальным.
Вот более подробный пример с комментариями:
let start = Date.now(); // запомнить время начала let timer = setInterval(function() { // сколько времени прошло с начала анимации? let timePassed = Date.now() - start; if (timePassed >= 2000) { clearInterval(timer); // закончить анимацию через 2 секунды return; } // отрисовать анимацию на момент timePassed, прошедший с начала анимации draw(timePassed); }, 20); // в то время как timePassed идёт от 0 до 2000 // left изменяет значение от 0px до 400px function draw(timePassed) { train.style.left = timePassed / 5 + 'px'; }
Здесь анимация длится 2 секунды. На каждой итерации вычисляется время с начала timePassed и вызывается функция отрисовки draw. Она изменяет смещение train.style.left пропорционально timePassed.
Таким образом, setInterval позволяет легко реализовать простую JavaScript-анимацию с фиксированным FPS.
Использование requestAnimationFrame
При создании нескольких анимаций на одной странице возникает проблема перегрузки процессора из-за чрезмерного количества кадров в секунду. Потому что компьютер воспринимает разные анимации как одно целое и в итоге прорисовывает их одновременно. Это может привести к перегревам и другим проблемам.
Например:
setInterval(function() { animate1(); animate2(); animate3(); }, 20)
Здесь запускаются три анимации в одном цикле с интервалом 20 мс (около 50 FPS). Это создаст высокую нагрузку на процессор.
Чтобы этого избежать, лучше запускать анимации независимо друг от друга:
setInterval(animate1, 20); // независимые анимации setInterval(animate2, 20); // в разных местах кода setInterval(animate3, 20);
Для более точного контроля анимаций используется функция requestAnimationFrame. Она запускает отрисовку кадра только при готовности браузера, оптимизируя нагрузку.
Синтаксис:
let requestId = requestAnimationFrame(callback)
В callback-функцию передается время отрисовки кадра, которая вызывается автоматически при готовности браузера. Она принимает единственное значение и выдает разрешение на новую анимацию, если выполнены следующие условия:
- не перегружен процессор;
- завершен тайминг предыдущего видео;
- достаточный заряд аккумулятора (у ноутбуков);
- и прочее.
Чтобы остановить анимацию, вызываем RequestId:
// отмена запланированного запуска callback cancelAnimationFrame(requestId);
Также можно задать интервал между запусками отдельных анимаций.
<script> let prev = performance.now(); let times = 0; requestAnimationFrame(function measure(time) { document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " "); prev = time; if (times++ < 10) requestAnimationFrame(measure); }) </script>
Здесь установлен стандартный таймер (порядка 15 мс), который блокирует запуск следующего видео сразу после предыдущего, для последовательности из десяти роликов. Таким образом requestAnimationFrame позволяет оптимально отрисовывать анимации, не перегружая браузер и процессор.
Структура анимации на JavaScript
Рассмотрим подробную структуру для создания сложной анимации на JavaScript:
function animate({timing, draw, duration}) { let start = performance.now(); requestAnimationFrame(function animate(time) { // timeFraction изменяется от 0 до 1 let timeFraction = (time - start) / duration; if (timeFraction > 1) timeFraction = 1; // вычисление текущего состояния анимации let progress = timing(timeFraction); draw(progress); // отрисовать её if (timeFraction < 1) { requestAnimationFrame(animate); } }); }
Здесь используются параметры, которые описывают структуру ролика:
- duration – продолжительность анимации;
- timing – функция тайминга, определяет текущий прогресс;
- draw – функция отрисовки кадра.
Эти параметры передаются в основную функцию animate при запуске.
Далее вычисляется текущая доля времени timeFraction от 0 до 1. Она будет учитываться при вызове функции timing, которая возвращает текущий прогресс анимации.
На каждом шаге запускается отрисовка кадра draw и планируется следующий шаг через requestAnimationFrame.
Простейший линейный тайминг:
function linear(timeFraction) { return timeFraction; }
График этой функции:
На графике видно, что прогресс идет равномерно. Чтобы улучшить «восприятие картинки», можно реализовать ускорение или замедление вывода слайдов, создавая тем самым динамические рывки или плавные переходы на экране.
Функция отрисовки (draw) для примера анимации элемента по горизонтали:
function draw(progress) { train.style.left = progress + 'px'; }
Функция draw может включать в себя различные кадры и эффекты, зависящие от значения progress, которое изменяется от 0 до 1. Также она может размножить исходный кадр и на его основе создавать новые, чтобы не пришлось рисовать каждый слайд по отдельности.
При помощи этих простейших функций мы можем анимировать любые изображения на экране, с детализацией вывода по кадрам. Дополнительное удобство заключается в том что все они вложены в одну общую процедуру animate.
А вот еще простой пример – анимирующий бегунок на экране:
animate({ duration: 1000, timing(timeFraction) { return timeFraction; }, draw(progress) { elem.style.width = progress * 100 + '%'; } });
В отличие от базовой CSS-анимации, использование JavaScript предоставляет гибкость и множество возможностей для создания сложных и динамичных анимаций.
Функции для определения темпа анимации
Темп анимации играет важную роль в ощущении, которое она создает. Вместо ручного задания каждого кадра мы можем использовать математические функции для создания различных эффектов движения.
Параболическое ускорение
Этот эффект создает ощущение ускорения объекта. Чтобы его реализовать, нужно возвести progress в степень n:
function quad(timeFraction) { return Math.pow(timeFraction, 2) }
Из этого кода получится квадратичная кривая, которая показывает график ускорения:
Например, если применить это к бегунку, то он будет ускоряться вдоль своей траектории.
Аналогично можно возводить в любую другую степень, и чем выше этот показатель, тем быстрее будет анимация.
Плавное замедление
Движение может начинаться и заканчиваться плавно, создавая эффект дуги:
function circ(timeFraction) { return 1 - Math.sin(Math.acos(timeFraction)); }
В этом случае график будет выглядеть следующим образом:
Эффект «выстрела из лука»
Сначала объект как бы «оттягивается» назад, а затем резко двигается вперед:
function back(x, timeFraction) { return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x) }
В отличие от предыдущей функции, теперь результат будет зависеть от дополнительного параметра Х – «коэффициента эластичности». Он определяет силу оттягивания.
Вот график для x = 1.5:
Эти функции – лишь вершина айсберга. JavaScript Animation позволяет создавать множество уникальных эффектов для реализации графических визуализаций. Когда такие функции применяются к большому числу объектов, создается иллюзия живого и динамичного виртуального мира. Это основа создания сложных 3D-анимаций, например, кат-сцен в компьютерных играх.
Примечание по использованию requestAnimationFrame и requestIdleCallback
Теперь рассмотрим возможности создания JavaScript-анимаций в браузере с точки зрения разных способов использования requestAnimationFrame и requestIdleCallback.
Изначально обе эти функции кажутся схожими, так как они направлены на оптимизацию работы анимаций и ограничение потребления аппаратных ресурсов. Однако их предназначение и способы использования различаются.
Благодаря requestAnimationFrame и requestIdleCallback разработчики получают гибкий инструментарий для создания анимаций прямо в браузере. Это обеспечивает высокую производительность и плавность графических визуализаций, аналогично тому, как если бы они работали с анимацией на сервере в облачной среде.
Суть обеих функций заключается в том, что они взаимодействуют с циклом событий браузера, оптимизируя процесс отрисовки визуализаций. Это позволяет создавать высококачественные анимации без необходимости дополнительной загрузки ресурсов или задержек.
В данном материале мы рассматриваем только основы и предоставляем некоторые базовые примеры работы с requestAnimationFrame и requestIdleCallback. Но если вы хотите углубиться в детали, вам стоит изучить дополнительные руководства по JavaScript Animation или просмотреть видеоуроки от экспертов в этой области.
Подробнее о requestAnimationFrame
requestAnimationFrame – это ключевой метод для создания анимаций в браузере. Именно он отвечает за покадровую отрисовку, что является фундаментом для анимированных роликов.
Одной из особенностей requestAnimationFrame является его эффективность. Он запускается единожды при инициализации анимации, оптимизируя использование системных ресурсов. Все дальнейшие действия происходят в рамках этой инициализации.
В качестве примера рассмотрим базовую реализацию, где пользователь может запустить анимацию с помощью кнопки:
<div class="box_for_animation"></div> <button class="button_for_animation">Start animation</button>
Пример создания внешнего вида окна для производства видео и кнопки запуска:
const animationBox = document.querySelector('.box_for_animation') const animationButton = document.querySelector('.button_for_animation') let animationStart let requestId
Чтобы вывести сохраненное видео в окно при помощи функции requestAnimationFrame, используем следующий код:
function startAnimation() { requestId = window.requestAnimationFrame(animate) animationButton.style.opacity = 0 }
Здесь мы используем requestAnimationFrame, чтобы инициировать анимацию. Она будет выполняться только после того, как браузер будет готов к следующему кадру, гарантируя максимально плавное воспроизведение. Это особенно полезно для обеспечения качественной анимации даже во всплывающих окнах или на сторонних сайтах.
Для улучшения пользовательского опыта давайте автоматически скроем кнопку запуска анимации после ее начала, чтобы избежать возможных путаниц:
animationButton.addEventListener('click', startAnimation, { once: true })
Таким образом, кнопка будет реагировать на клик только один раз, предотвращая повторный запуск анимации.
Также нужно добавить одноразовый обработчик события click:
function animate(timestamp) { if (!animationStart) { animationStart = timestamp } const progress = timestamp - animationStart animationBox.style.transform = `translateX(${progress / 5}px)` const x = animationBox.getBoundingClientRect().x + 100 // 6px - scrollbar width if (x <= window.innerWidth - 6) { window.requestAnimationFrame(animate) } else { window.cancelAnimationFrame(requestId) } }
В приведенном коде функция animate использует переданный ей таймстамп для определения времени, прошедшего с начала анимации. Это позволяет корректно обрабатывать ситуации, когда пользователь выводит курсор за пределы окна или осуществляет другие действия, которые могут привести к прерыванию анимации. Благодаря этому подходу анимация экономит ресурсы и предотвращает нежелательные эффекты, например, «дрожание» изображения.
Функция raF также предоставляет возможность зацикливания анимации. Работая в синхронии со встроенным таймером браузера, она обеспечивает покадровое воспроизведение, создавая иллюзию непрерывного движения.
Кроме того, стоит отметить, что анимация может быть синхронизирована с другими браузерными событиями, такими как прокрутка:
let scheduledAnimationFrame // читаем и обновляем страницу function readAndUpdatePage(){ console.log('read and update') scheduledAnimationFrame = false } function onScroll () { // сохраняем значение прокрутки для будущего использования const lastScrollY = window.scrollY // предотвращаем множественный вызов колбека, переданного `rAF` if (scheduledAnimationFrame) { return } scheduledAnimationFrame = true window.requestAnimationFrame(readAndUpdatePage) } window.addEventListener('scroll', onScroll)
В вышеуказанном коде scheduledAnimationFrame выполняет роль флага и контролирует процесс воспроизведения анимации во время прокрутки. Чтобы максимизировать производительность и экономить аппаратные ресурсы, проверка состояния scheduledAnimationFrame осуществляется только один раз при начале прокрутки.
Подробнее о requestIdleCallback
requestIdleCallback – это механизм, который позволяет браузеру выполнять задачи в «свободное» время, когда нет активных действий, таких как анимации или взаимодействие с пользователем. Основная идея заключается в том, чтобы максимально эффективно использовать ресурсы компьютера, не мешая основным действиям.
В отличие от requestAnimationFrame, который фокусируется на анимациях и работает с высоким приоритетом, requestIdleCallback предназначен для задач второстепенного плана. Например, обработка данных, обновление DOM в фоновом режиме или предварительная загрузка контента. Система автоматически выделяет до 50 миллисекунд для таких задач, чтобы не мешать основным процессам.
Для понимания, как это работает на практике, давайте рассмотрим пример совместной работы requestAnimationFrame (raF) и requestIdleCallback (rIC), которые контролируют создание и отображение различных DOM-элементов.
<div class="buttons"> <button data-type="square" class="button">Create square</button> <button data-type="polygon" class="button">Create polygon</button> <button data-type="circle" class="button">Create circle</button> <button data-type="render" class="button">Render shapes</button> </div> <div class="stat"> <p>Squares: <span class="counter" data-for="square">0</span></p> <p>Polygons: <span class="counter" data-for="polygon">0</span></p> <p>Circles: <span class="counter" data-for="circle">0</span></p> </div>
В этом примере мы предоставляем пользователю возможность создавать разные геометрические фигуры и отслеживать их количество.
// ссылки на DOM-элементы const shapeButtons = document.querySelector('.buttons') const statBox = document.querySelector('.stat') // поисковая таблица для значений счетчиков const counterByShape = { square: statBox.querySelector("[data-for='square']"), polygon: statBox.querySelector("[data-for='polygon']"), circle: statBox.querySelector("[data-for='circle']") } // см. ниже let nextUnitOfWork = null let shapesToRender = [] let render = false let randomShape = true // поисковая таблица для определения следующей (произвольной) фигуры const randomShapeMap = { square: 'polygon', polygon: 'circle', circle: 'square' }
Этот код подготавливает основу для дальнейших действий, таких как создание, рендеринг и управление геометрическими фигурами с помощью raF и rIC.
При этом, благодаря использованию requestIdleCallback, браузер будет выполнять такие задачи в «свободное» время, когда нет других активных действий. И в этом заключается одно из его преимуществ – низкий приоритет выполнения задач. Это означает, что даже на слабом компьютере процессы, которые обрабатываются с помощью rIC, не будут создавать существенной нагрузки на систему, поскольку они будут выполнены только тогда, когда это не мешает другим, более приоритетным задачам.
Рассмотрим четыре ключевых параметра, которые обеспечивают гибкость и контроль над процессом рендеринга:
- nextUnitOfWork – это указатель на следующую задачу, ожидающую выполнения во время простоя браузера.
- shapesToRender – это массив, в котором хранятся все созданные фигуры, ожидающие рендеринга на экране.
- render – это булево значение (логический тип), которое определяет, следует ли начать процесс рендеринга.
- randomShape – это параметр, определяющий, следует ли создавать случайную фигуру.
Описанные параметры позволяют сохранять историю всех манипуляций с объектами: их перемещение, изменение размера и другие действия. Это демонстрирует, как можно реализовывать сложные анимации и интерактивные элементы без создания значительной нагрузки на процессор.
Давайте рассмотрим функцию performUnitOfWork, которая позволяет нам создавать новые фигуры:
window.requestIdleCallback = window.requestIdleCallback || function (handler) { const start = Date.now() return setTimeout(() => { handler({ didTimeout: false, timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) }) }, 1) }
В этой функции мы создаем новую фигуру на основе переданного типа (например, квадрат, многоугольник или круг) и добавляем ее в массив shapesToRender. Также мы обновляем соответствующий счетчик на экране. Если параметр randomShape установлен в true, следующая фигура будет выбираться в случайном порядке из randomShapeMap.
Таким образом, благодаря requestIdleCallback, даже сложные анимации и интерактивные элементы можно создавать эффективно и без избыточной нагрузки на процессор.
Заключение
Современные программные технологии requestAnimationFrame и requestIdleCallback, предоставляют разработчикам мощный набор инструментов для оптимизированной работы с видеоконтентом. Эти методы учитывают аппаратные возможности устройства, предотвращая его избыточную нагрузку, и при этом дают возможность реализовывать сложные и эффектные анимации.
Используя эти инструменты, можно достичь высокого качества воспроизведения, не ущемляя производительность устройства, что особенно ценно для мобильных платформ и слабых ПК. Таким образом, даже самые амбициозные проекты становятся реализуемыми без потери в производительности.
Комментарии