- PVSM.RU - https://www.pvsm.ru -

JavaScript и кое-что ещё: 4 креативных подхода к измерению времени в браузерах

Автор статьи, перевод которой мы сегодня публикуем, решил рассказать о нескольких необычных способах измерения времени в браузерах. Для их использования понадобится доступ к различным API, которые применяются в веб-разработке, поэтому они не подходят для платформы Node.js. Правда, если кто-то нуждается в необычном способе измерения времени в Node.js, то, полагаем, после прочтения этого материала у него могут появиться кое-какие идеи на этот счёт.

JavaScript и кое-что ещё: 4 креативных подхода к измерению времени в браузерах - 1 [1]

Использование бесконечного синхронного цикла в веб-воркере (не в сервис-воркере)

Так как веб-воркеры, в сущности, представляют собой потоки, работающие в веб-браузере, в них можно запускать бесконечные циклы, не рискуя заблокировать главный поток. Это позволяет работать с отрезками времени, длительность которых составляет менее миллисекунды. Такая точность особенно хорошо подходит для тех случаев, когда в воркере нужно принимать решения, которые сильно зависят от времени. О принятии этих решений можно (с очень высоким уровнем точности) сообщать в главный поток. Например, можно что-то выводить в том случае, когда количество прошедших с некоего события микросекунд представлено простым числом. Для того чтобы работать со временем с микросекундной точностью, можно воспользоваться методом performance.now [2].

▍Достоинства

  1. Достижима точность, выражаемая микросекундами.
  2. На главный поток не ложится практически никакой нагрузки.
  3. Полностью асинхронный, с точки зрения главного потока, механизм измерения времени. Это возможно благодаря особенностям устройства системы обмена сообщениями между потоком веб-воркера и главным потоком.
  4. Безопасное завершение работы. В отличие от неопределённости, сопряжённой с использованием setInterval, вызов метода воркера terminate гарантирует то, что после завершения работы воркера от него больше не будут поступать сообщения со сведениями о времени. В MDN по этому поводу говорится следующее: «Метод terminate() интерфейса Worker немедленно завершает работу воркера. Ему не дается возможность завершить свою работу, он останавливается сразу».

▍Недостатки

  1. Даже хотя этот метод даёт возможность работать с интервалами времени, которые меньше миллисекунды, отправка сообщений в главный поток работает асинхронно. То есть, нельзя произвести некие действия в главном потоке с той же точностью, с которой в воркере принимается решение о том, что эти действия нужно произвести.
  2. Этот метод полностью загружает поток, что может привести к повышенному расходу заряда аккумуляторов на телефонах.
  3. Для работы этого метода нужна поддержка веб-воркеров.
  4. Бесконечный цикл в веб-воркере не приостанавливается в том случае, если вкладка, связанная с ним, неактивна.

Пример [3] на Codesandbox

Использование CSS-анимаций ради событий, связанных со временем (в частности — animationiteration)

Этот метод подразумевает создание некоего элемента с бесконечной анимацией. Тут можно попробовать воспользоваться элементом div, но надо сказать, что, как отмечено во 2 пункте перечня недостатков этого метода, элементами div для этих целей лучше не пользоваться. Итак, если у нас имеется элемент с бесконечной анимацией, мы можем подписаться на его событие animationiteration и получать уведомления в те моменты, когда будет истекать интервал animation-duration.

▍Достоинства

  1. Автоматическая приостановка в том случае, если браузерная вкладка становится неактивной. В таком случае интересующее нас событие просто не происходит. Поэтому не нужно заботиться о решении проблемы обработки множества событий, накопившихся за время, когда вкладка была неактивной, а потом была активирована.
  2. Автоматическое освобождение ресурсов при удалении скрытого элемента div из DOM. Например, если имеется React-компонент, который выводит время, то ничего особенного при его отмонтировании делать не нужно. Элемент div будет удалён и событие больше вызываться не будет.
  3. Этот плюс данного метода субъективен, но надо отметить, что логика подписки выглядит просто прекрасно. Например — так:
    .addEventListener("animationiteration", fun).
  4. Чистейший способ откладывания запуска таймера с использованием animation-delay.

▍Недостатки

  1. Этот метод не отличается интуитивной понятностью. Его использование может запутать других программистов, работающих над тем проектом, где он применяется.
  2. Зависимость от DOM и CSSOM. Другие CSS-правила могут повлиять на те, которые описывают анимацию. Поэтому я советую создать для целей организация таймера некий произвольно названный ранее несуществующий тег вроде <just-a-timer-element></<just-a-timer-element>. Возможно — стоит создать пользовательский элемент, внутри которого аккуратно спрятан код CSS-анимации? (всё это — довольно спорные идеи, на самом деле).
  3. Этот метод не работает в том случае, если у элемента есть стиль display: none;.
  4. Неточность. В соответствии с проведёнными мной испытаниями, точность отсчёта времени может быть около 1 мс. Вы, чтобы выяснить, как именно это работает у вас, можете поэкспериментировать с примером, ссылка на который приведена ниже.

Пример [4] на Codesandbox

Использование тега svg (SMIL-анимации)

Взгляните на следующий SVG-элемент:

<svg>
  <rect>
    <animate
      attributeName="rx"
      values="0;1"
      dur="1s"
      repeatCount="indefinite"
    />
  </rect>
</svg>

Если воспользоваться следующим кодом: animate.addEventListener('repeat', fun), то функция fun будет вызываться каждые dur секунд. В нашем случае — каждую секунду.

▍Достоинства

  1. Этот метод работает даже в том случае, если SVG-элементу назначен стиль display: none;.
  2. Отсчёт времени автоматически останавливается тогда, когда SVG-элемент удаляется из DOM.
  3. Генерирование событий не начинается до полной загрузки страницы.
  4. «Таймер» автоматически приостанавливается в том случае, если вкладка становится неактивной.

▍Недостатки

  1. Как и в случае с отсчётом временных интервалов с использованием CSS-анимации, применение этого метода может показаться непонятным другим программистам.
  2. Зависимость от DOM и CSSOM. То есть — тут мы имеем те же нежелательные особенности, что уже были описаны для метода отсчёта времени с помощью CSS-анимации. А именно — возможность нарушения работы «таймера» из-за других CSS-правил.
  3. Не поддерживается в IE и Edge (до перевода браузера от Microsoft на Chromium).
  4. Неточность. В соответствии с моими испытаниями, отклонения такого таймера от самого точного метода могут составлять весьма внушительные 15 миллисекунд. Можете сами это проверить, поэкспериментировав с примером, ссылка на который дана ниже.
  5. Отсчёт времени не начинается до полной загрузки страницы. Правда, этот «минус» данного метода может в какой-нибудь ситуации оказаться и «плюсом».

Пример [5] на Codesandbox

Использование Web Animations API

Web Animations API [6] позволяет анимировать DOM-элементы средствами JavaScript-кода.

Интересно то, что можно анимировать даже отмонтированные элементы! Это даёт доступ к механизмам работы со временем, доступным в чистом JavaScript (и в Web API).

Вот альтернативная реализация setTimeout:

function ownSetTimeout(callback, duration) {
  const div = document.createElement('div');

  const keyframes = new KeyframeEffect(div, [], { duration, iterations: 1 });

  const animation = new Animation(
    keyframes,
    document.timeline
  );

  animation.play();

  animation.addEventListener('finish', () => {
    callback();
  });
}

Аккуратно, правда?

▍Достоинства

  1. Самодостаточное решение, не требующее взаимодействия с DOM.
  2. Незнакомый с этим подходом программист легко поймёт смысл соответствующего кода.
  3. «Таймер» приостанавливается на неактивной вкладке.

▍Недостатки

  1. Web Animations API — технология всё ещё экспериментальная. Не стоит пользоваться ей в продакшне.
  2. Очень плохая браузерная поддержка. Этот метод, вероятно, будет работать лишь в Chromium.
  3. При всех плюсах этого решения, оно, всё же, может показаться кому-то далёким от интуитивной понятности.
  4. То, что «таймер» приостанавливается на неактивной вкладке, может оказаться минусом этого решения в том случае, если оно используется как альтернатива setTimeout.
  5. Этот метод нельзя использовать для отсчёта интервалов. Программисту доступно лишь событие onfinish.
  6. Невысокая точность. В соответствии с моими экспериментами, ошибка легко может составлять ±5 миллисекунд.

→ Пример [7] на Codesandbox

Бонус

Для того чтобы работать со временем, можно воспользоваться Web Audio API. Это — ещё один замечательный способ точной работы с интервалами и задержками. Вот [8] отличная статья об этом.

Итоги

Я понимаю, что методики работы со временем, которые я тут перечислил, способны принести пользу далеко не всем. Но я просто не мог не написать эту статью, так как всегда думал, что setTimeout и setInterval — это единственные способы для асинхронной работы с некими отрезками времени. А на самом деле, как оказалось, это — далеко не всё. Кто знает, возможно, кому-то придётся столкнуться с какими-то необычными ограничениями при работе над неким проектом, с какими-то особыми условиями, в которых освещённые здесь методы работы со временем могут оказаться полезными.

Уважаемые читатели! Как вы думаете, в каких ситуациях могут пригодиться описанные здесь подходы работы со временем?

Автор: ru_vds

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/347797

Ссылки в тексте:

[1] Image: https://habr.com/ru/company/ruvds/blog/489818/

[2] performance.now: https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

[3] Пример: https://codesandbox.io/s/web-worker-timer-eiczf

[4] Пример: https://codesandbox.io/s/css-animation-timer-fvssk

[5] Пример: https://codesandbox.io/s/svg-animate-as-timer-u8zun

[6] Web Animations API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API

[7] Пример: https://codesandbox.io/s/web-animations-api-as-a-timer-duv5q

[8] Вот: https://www.html5rocks.com/en/tutorials/audio/scheduling/

[9] Источник: https://habr.com/ru/post/489818/?utm_source=habrahabr&utm_medium=rss&utm_campaign=489818