Автор статьи, перевод которой мы сегодня публикуем, решил рассказать о нескольких необычных способах измерения времени в браузерах. Для их использования понадобится доступ к различным API, которые применяются в веб-разработке, поэтому они не подходят для платформы Node.js. Правда, если кто-то нуждается в необычном способе измерения времени в Node.js, то, полагаем, после прочтения этого материала у него могут появиться кое-какие идеи на этот счёт.
Использование бесконечного синхронного цикла в веб-воркере (не в сервис-воркере)
Так как веб-воркеры, в сущности, представляют собой потоки, работающие в веб-браузере, в них можно запускать бесконечные циклы, не рискуя заблокировать главный поток. Это позволяет работать с отрезками времени, длительность которых составляет менее миллисекунды. Такая точность особенно хорошо подходит для тех случаев, когда в воркере нужно принимать решения, которые сильно зависят от времени. О принятии этих решений можно (с очень высоким уровнем точности) сообщать в главный поток. Например, можно что-то выводить в том случае, когда количество прошедших с некоего события микросекунд представлено простым числом. Для того чтобы работать со временем с микросекундной точностью, можно воспользоваться методом performance.now.
▍Достоинства
- Достижима точность, выражаемая микросекундами.
- На главный поток не ложится практически никакой нагрузки.
- Полностью асинхронный, с точки зрения главного потока, механизм измерения времени. Это возможно благодаря особенностям устройства системы обмена сообщениями между потоком веб-воркера и главным потоком.
- Безопасное завершение работы. В отличие от неопределённости, сопряжённой с использованием
setInterval
, вызов метода воркераterminate
гарантирует то, что после завершения работы воркера от него больше не будут поступать сообщения со сведениями о времени. В MDN по этому поводу говорится следующее: «Методterminate()
интерфейсаWorker
немедленно завершает работу воркера. Ему не дается возможность завершить свою работу, он останавливается сразу».
▍Недостатки
- Даже хотя этот метод даёт возможность работать с интервалами времени, которые меньше миллисекунды, отправка сообщений в главный поток работает асинхронно. То есть, нельзя произвести некие действия в главном потоке с той же точностью, с которой в воркере принимается решение о том, что эти действия нужно произвести.
- Этот метод полностью загружает поток, что может привести к повышенному расходу заряда аккумуляторов на телефонах.
- Для работы этого метода нужна поддержка веб-воркеров.
- Бесконечный цикл в веб-воркере не приостанавливается в том случае, если вкладка, связанная с ним, неактивна.
→ Пример на Codesandbox
Использование CSS-анимаций ради событий, связанных со временем (в частности — animationiteration)
Этот метод подразумевает создание некоего элемента с бесконечной анимацией. Тут можно попробовать воспользоваться элементом div
, но надо сказать, что, как отмечено во 2 пункте перечня недостатков этого метода, элементами div
для этих целей лучше не пользоваться. Итак, если у нас имеется элемент с бесконечной анимацией, мы можем подписаться на его событие animationiteration
и получать уведомления в те моменты, когда будет истекать интервал animation-duration
.
▍Достоинства
- Автоматическая приостановка в том случае, если браузерная вкладка становится неактивной. В таком случае интересующее нас событие просто не происходит. Поэтому не нужно заботиться о решении проблемы обработки множества событий, накопившихся за время, когда вкладка была неактивной, а потом была активирована.
- Автоматическое освобождение ресурсов при удалении скрытого элемента
div
из DOM. Например, если имеется React-компонент, который выводит время, то ничего особенного при его отмонтировании делать не нужно. Элементdiv
будет удалён и событие больше вызываться не будет. - Этот плюс данного метода субъективен, но надо отметить, что логика подписки выглядит просто прекрасно. Например — так:
.addEventListener("animationiteration", fun).
- Чистейший способ откладывания запуска таймера с использованием
animation-delay
.
▍Недостатки
- Этот метод не отличается интуитивной понятностью. Его использование может запутать других программистов, работающих над тем проектом, где он применяется.
- Зависимость от DOM и CSSOM. Другие CSS-правила могут повлиять на те, которые описывают анимацию. Поэтому я советую создать для целей организация таймера некий произвольно названный ранее несуществующий тег вроде
<just-a-timer-element></<just-a-timer-element>
. Возможно — стоит создать пользовательский элемент, внутри которого аккуратно спрятан код CSS-анимации? (всё это — довольно спорные идеи, на самом деле). - Этот метод не работает в том случае, если у элемента есть стиль
display: none;
. - Неточность. В соответствии с проведёнными мной испытаниями, точность отсчёта времени может быть около 1 мс. Вы, чтобы выяснить, как именно это работает у вас, можете поэкспериментировать с примером, ссылка на который приведена ниже.
→ Пример на Codesandbox
Использование тега svg (SMIL-анимации)
Взгляните на следующий SVG-элемент:
<svg>
<rect>
<animate
attributeName="rx"
values="0;1"
dur="1s"
repeatCount="indefinite"
/>
</rect>
</svg>
Если воспользоваться следующим кодом: animate.addEventListener('repeat', fun)
, то функция fun
будет вызываться каждые dur
секунд. В нашем случае — каждую секунду.
▍Достоинства
- Этот метод работает даже в том случае, если SVG-элементу назначен стиль
display: none;
. - Отсчёт времени автоматически останавливается тогда, когда SVG-элемент удаляется из DOM.
- Генерирование событий не начинается до полной загрузки страницы.
- «Таймер» автоматически приостанавливается в том случае, если вкладка становится неактивной.
▍Недостатки
- Как и в случае с отсчётом временных интервалов с использованием CSS-анимации, применение этого метода может показаться непонятным другим программистам.
- Зависимость от DOM и CSSOM. То есть — тут мы имеем те же нежелательные особенности, что уже были описаны для метода отсчёта времени с помощью CSS-анимации. А именно — возможность нарушения работы «таймера» из-за других CSS-правил.
- Не поддерживается в IE и Edge (до перевода браузера от Microsoft на Chromium).
- Неточность. В соответствии с моими испытаниями, отклонения такого таймера от самого точного метода могут составлять весьма внушительные 15 миллисекунд. Можете сами это проверить, поэкспериментировав с примером, ссылка на который дана ниже.
- Отсчёт времени не начинается до полной загрузки страницы. Правда, этот «минус» данного метода может в какой-нибудь ситуации оказаться и «плюсом».
→ Пример на Codesandbox
Использование Web Animations API
Web Animations API позволяет анимировать 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();
});
}
Аккуратно, правда?
▍Достоинства
- Самодостаточное решение, не требующее взаимодействия с DOM.
- Незнакомый с этим подходом программист легко поймёт смысл соответствующего кода.
- «Таймер» приостанавливается на неактивной вкладке.
▍Недостатки
- Web Animations API — технология всё ещё экспериментальная. Не стоит пользоваться ей в продакшне.
- Очень плохая браузерная поддержка. Этот метод, вероятно, будет работать лишь в Chromium.
- При всех плюсах этого решения, оно, всё же, может показаться кому-то далёким от интуитивной понятности.
- То, что «таймер» приостанавливается на неактивной вкладке, может оказаться минусом этого решения в том случае, если оно используется как альтернатива
setTimeout
. - Этот метод нельзя использовать для отсчёта интервалов. Программисту доступно лишь событие
onfinish
. - Невысокая точность. В соответствии с моими экспериментами, ошибка легко может составлять ±5 миллисекунд.
→ Пример на Codesandbox
Бонус
Для того чтобы работать со временем, можно воспользоваться Web Audio API. Это — ещё один замечательный способ точной работы с интервалами и задержками. Вот отличная статья об этом.
Итоги
Я понимаю, что методики работы со временем, которые я тут перечислил, способны принести пользу далеко не всем. Но я просто не мог не написать эту статью, так как всегда думал, что setTimeout
и setInterval
— это единственные способы для асинхронной работы с некими отрезками времени. А на самом деле, как оказалось, это — далеко не всё. Кто знает, возможно, кому-то придётся столкнуться с какими-то необычными ограничениями при работе над неким проектом, с какими-то особыми условиями, в которых освещённые здесь методы работы со временем могут оказаться полезными.
Уважаемые читатели! Как вы думаете, в каких ситуациях могут пригодиться описанные здесь подходы работы со временем?
Автор: ru_vds