HTML часы идут неравномерно

Рейтинг: 3Ответов: 2Опубликовано: 27.03.2023

У меня на странице есть следующие часы:

function change_time(field){
    field.textContent = new Date().toLocaleTimeString()
}

let date_time_field = document.querySelector('.preview_date_time')
setInterval(change_time, 500, date_time_field)
<div class="preview_date_time"></div>

Они обновляются каждые 500 миллисекунд. Когда время в системных часах кратно 500 миллисекундам, секунды на странице могут сменяться неравномерно. Например, секунда может смениться чаще, чем раз в 1000 миллисекунд. Понаблюдайте за таймером секунд 20 и вы поймёте о чём я говорю.
Подскажите пожалуйста, как я могу избавиться от дёрганья времени на странице?

Ответы

▲ 2Принят

Можно решить задачу с минимальной тратой процессора, если отказаться от setInterval в пользу setTimeout.

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

const field = document.querySelector('.preview_date_time');
const next_tick = () => {
    const date = new Date();
    field.textContent = date.toLocaleTimeString();
    setTimeout(next_tick, 10 + 1000 - date.getMilliseconds());
};
next_tick();
<div class="preview_date_time"></div>

P.S. По стандарту можно было бы не прибавлять десять миллисекунд к началу следующей секунды. Задержка должна быть не меньше заданной - раньше начала секунды вызова быть не может. Но я не уверен что браузеры строго следуют стандарту, поэтому лучше прибавить.

▲ 0

Вообще, такое происходит из-за того что на самом деле интервал не ровный, часто бывает что интервал опаздывает на пару (или больше) миллисекунд. Можно в этом убедиться:

function checkTime() {
  const now = Date.now()

  return new Promise(res => {
    setTimeout(() => {
      const timePassed = Date.now() - now

      res(timePassed)
    }, 500)
  })
}

checkTime().then(time => {
  console.log('Прошло ' + time + ' миллисекунд')
})

Можно сделать вот так:

function change_time(field) {
  field.textContent = new Date().toLocaleTimeString()
}

let date_time_field = document.querySelector('.preview_date_time')

requestAnimationFrame(function onUpdate() {
  change_time(date_time_field)

  requestAnimationFrame(onUpdate)
})
<div class="preview_date_time"></div>

Но насчёт производительности ничего не обещаю. Всё таки обновлять элемент, с той же частотой, как и обновляется монитор, дело такое.