createCountdown
возвращает объект обратного отсчета двумя методами: start
и stop
.
Обратный отсчет имеет дату to
, обратный вызов onTick
и granularity
.
granularity
- это частота, с которой вызывается обратный вызов onTick
. Поэтому, если вы установите гранулярность 1000 мс, отсчет будет происходить только один раз в секунду.
Когда разница между now
и to
равна нулю, вызывается обратный вызов onComplete
, и это скрывает узел DOM.
В этом решении используется requestAnimationFrame
, максимальное разрешение которого составляет около 16 миллисекунд. Учитывая, что это максимальная скорость обновления экрана, это хорошо для наших целей.
const $ = document.querySelector.bind(document)
const now = Date.now
const raf = requestAnimationFrame
const caf = cancelAnimationFrame
const defaultText = '--:--:--:--'
const createCountdown = ({ to, onTick, onComplete = () => {}, granularityMs = 1, rafId = null }) => {
const start = (value = to - now(), grain = null, latestGrain = null) => {
const tick = () => {
value = to - now()
if(value <= 0) return onTick(0) && onComplete()
latestGrain = Math.trunc(value / granularityMs)
if (grain !== latestGrain) onTick(value)
grain = latestGrain
rafId = raf(tick)
}
rafId = raf(tick)
}
const stop = () => caf(rafId)
return { start, stop }
}
const ho = (ms) => String(Math.trunc((ms/1000/60/60))).padStart(2, '0')
const mi = (ms) => String(Math.trunc((ms%(1000*60*60))/60000)).padStart(2, '0')
const se = (ms) => String(Math.trunc((ms%(1000*60))/1000)).padStart(2, '0')
const ms = (ms) => String(Math.trunc((ms%(1000)))).padStart(3, '0')
const onTick = (value) => $('#output').innerText = `${ho(value)}:${mi(value)}:${se(value)}:${ms(value)}`
const onComplete = () => $('#toFade').classList.add('hidden')
const to = Date.now() + (2 * 60 * 1000)
const { start, stop } = createCountdown({ to, onTick, onComplete })
$('button#start').addEventListener('click', start)
$('button#stop').addEventListener('click', () => (stop(), $('#output').innerText = defaultText))
div#toFade {
opacity: 1;
transition: opacity 5s linear 0s;
}
div#toFade.hidden {
opacity: 0;
}
div {
padding: 20px;
}
<button id="start">Start</button>
<button id="stop">Stop</button>
<div id="output">--:--:--:--</div>
<div id="toFade">This is the element to fade out.</div>