HTML / CSSJavaScriptNode jsПаттерны проектированияПрактические

setTimeout, setInterval и requestAnimationFrame

Таймеры setTimeout и setInterval

Каждый блок JavaScript кода, как правило, выполняется синхронно. Но в коробке у JavaScript уже есть нативные функции (таймеры), которые позволяют задерживать выполнение какого-либо кода. Это setTimeout() и setInterval(). Они позволят вам запустить кусок JavaScript кода в определенный момент в будущем. Такой подход называется “отложенным вызовом”.

setTimeout()

setTimeout - используется в основном в тех случаях, если вы хотите запустить вашу функцию через конкретное количество миллисекунд после вызова самого setTimeout().

Синтаксис для этого метода такой:

1setTimeout ( callback, timeout );

Тут callback в JavaScript коде запустится по прошествии миллисекунд, указанных в аргументе timeout. setTimeout() Также возвращает ID для тайм-аута, чтобы его можно было отследить. Но в основном оно используется для метода clearTimeout(), который останавливает выполнение отложенной функции. В качестве аргумента тут нужно вставить ID(название) функции.

Код ниже, вызывает sayHello() через одну секунду:

1function sayHello() {
2alert('Hello');
3}
4setTimeout(sayHello, 1000);

Вы можете также передавать аргументы вместе с функцией, например как тут:

1function sayHello(message, person) {
2alert( message + ', '+ person );
3}
4setTimeout(sayHello, 1000, "Hi", "Monica"); // Hi, Monica

Как вы видите, для setTimeout() сначала передаётся функция аргумент, затем время задержки и уже только потом аргументы для функции аргумента. Если первый аргумент это строка, то JavaScript может создать из неё функцию. Так что вот это тоже сработает:

1setTimeout("alert('Hello')", 1000);

Но применение такого метода не рекомендуется, лучше используйте функции как тут:

1setTimeout(function(){
2 alert('Hello')
3}, 1000);

setInterval()

Эта функция, в основном используется для задержки функций, которые будут выполняться снова и снова, например анимации. Функция setInterval() очень близка к setTimeout(), у них даже такой же синтаксис:

1setInterval ( callback, interval );

Но разница тут вот в чём. setTimeout() запускает callback только единожды, в то время, как setInterval() продолжает запускать callback на регулярной основе после заданного временного интервала, пока вы не скажете стоп. Для того, чтобы остановить последующие вызовы в setInterval(), вам нужно вызывать clearInterval(timerId), где timerId это имя функции setInterval.

1// Hello показывается каждые 3 секунды
2let timerId= setInterval(() => alert('Hello'), 3000);
3// Повторения прекращаются после 6 секунд с id таймера.
4setTimeout(() => { clearInterval(timerId); alert('Bye'); }, 6000);

Когда вам нужно использовать setInterval() ? Когда вам не нужно вызывать setTimeout() в конце спланированной функции. Также, во время использования setInterval(), фактически не существует задержки между одним срабатыванием настоящего выражения и последующим. А в setTimeout() существует относительно долгая задержка, во время выполнения выражения, вызова функции и выставления нового setTimeout. Так что если вам нужен обычный точный таймер и надо, чтобы что-то делалось повторно после определенного временного интервала, тогда setInterval это ваш выбор.

requestAnimationFrame()

Использование requestAnimationFrame позволяет браузеру справиться с некоторыми затруднительными задачами связанными с анимацией, например такими как управление частотой кадров. До этого разработчики использовали setTimeout и setInterval, чтобы создавать анимации. Проблема тут была в том, что для того, чтобы анимации были плавными, браузер зачастую отрисовывал кадры быстрее, чем они могут показаться на экране. Что вело к ненужным вычислениям. Также ещё одной проблемой в использовании setInterval или setTimeout было то, что эти анимации продолжали работать, даже если страница не находилась в поле видимости пользователя.

Почему нужно использовать requestAnimationFrame?

  • Оптимизация браузером Использование requestAnimationFrame даёт браузеру возможность оптимизировать анимации, чтобы делать их плавнее и более ресурсоэффективными. Не будем сильно вдаваться в детали того, как браузер это делает, просто знайте, что requestAnimationFrame исключает возможность ненужных отрисовок и может связывать вместе несколько анимаций в одно целое и цикл перерисовки.

  • Анимации работают, когда их видно Используя requestAnimationFrame анимации будут работать только тогда, когда вкладка со страницей видима пользователю. А это означает меньшее CPU, GPU и использование памяти, что приводит нас к последнему моменту эффективности.

  • Меньшее потребление питания Оптимизации, упомянутые в предыдущих двух моментах помогают сократить количество “мусорных процессов”, которые нужно совершить устройству, чтобы создать анимацию и, следовательно, это ведет к бережному энергопотреблению. Это особенно важно для мобильных устройств, которые обычно имеют относительно короткие сроки работы батареи. Используем requestAnimationFrame Этому методу должна быть передана колбэк функция, которая отвечает за отрисовку одного кадра вашей анимации. Для того, чтобы создать полную анимацию, вам понадобится сделать этот колбэк рекурсивным. Временная метка с высоким разрешением DOMHighResTimeStamp передаётся колбэку. Вам не понадобится всегда это использовать, но это может быть довольно полезным для некоторых анимаций. Пример ниже показывает то, как настроить рекурсивную функцию, которая использует requestAnimationFrame.

1// Анимируем
2function animate(highResTimestamp) {
3requestAnimationFrame(animate);
4// Анимируем что-нибудь…
5}
6// Запускаем анимацию.
7requestAnimationFrame(animate);

Стоит упомянуть, что у вас есть только 16.67 миллисекунд (60fps), чтобы отрендерить каждый кадр. С точки зрения времени это не очень хорошо, так что вам нужно быть осторожным с тем, что вы хотите выполнить внутри колбэк функции. Если ваш кадр требует больше 16.67 секунд на обработку, то анимация может выйти не совсем плавной. requestAnimationFrame Отдаст requestID, который может быть использован для отмены запланированного кадра анимации.

1var requestID = requestAnimationFrame(animate);

Чтобы отменить кадр анимации вы можете использовать метод cancelAnimationFrame. Этот метод должен принять requestID для кадра, который вы хотите отменить.

1cancelAnimationFrame(requestID);

Практика 👩‍💻 👨‍💻

Countdown

Написать функцию конструктор (или Класс) Countdown С методами:

  • Start
  • Stop
  • Reset

Конструктор должен принимать следующие аргументы

  • callback - функция которая должны вызваться каждый промежуток указанный переменной time
  • time - интервал через который должна вызываться функция callback
1function Countdown(callback, time){
2 this.start
3 this.stop
4}
  1. добавить статический метод getInstanceLength который возвращает количество созданных екземпляров
  2. добавить статический метод stopAll который останавливает все активные таймеры

Дополнительно (Отдельная оценка)

Написать кастомные события

  1. done вызывается когда таймер, заканчивает свою работу
  2. start вызывается когда таймер, начинает свою работу
1function Countdown(callback, time){
2}
3
4
5const countdown1 = new Countdown()
6const countdown2 = new Countdown()
7function onStart(timerId){
8 console.log('timer started with id',timerId)
9}
10function onDone(){
11 console.log('timer done with id',timerId)
12}
13// Timer 1
14countdown1.on('start',onStart)
15countdown1.on('done',onStart)
16// // Timer 2
17countdown2.on('start',onStart)
18countdown2.on('done',onStart)
Hello