Следующая реализация имеет два элемента состояния:
dateString
- строковое представление времени offset
- текущее выбранное смещение времени
tick
многократно обновляет dateString
, чтобы зафиксировать ход времени.
render
отображает dateString
в DOM, используя requestAnimationFrame
.
const freeze = Object.freeze
const DTF = new Intl.DateTimeFormat('en', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })
let state = freeze({
dateString: '00:00:00',
offset: 0
})
function setOffset(o) {
clearTimeout(tickId)
state = freeze({ ...state, offset: o })
tick()
}
function toDateString(date = new Date) {
const d = DTF.formatToParts(date)
return `${d[0].value}:${d[2].value}:${d[4].value}`
}
let tickId = null
function tick() {
const date = new Date
date.setHours(date.getHours() + state.offset)
const dateString = toDateString(date)
tickId = setTimeout(tick, 16)
if(dateString !== state.dateString)
state = freeze({ ...state, dateString })
}
function render(state) {
document.getElementById('time').innerText = state.dateString
}
let previousState = null
function loop() {
if(state !== previousState) {
previousState = state
render(state)
}
requestAnimationFrame(loop)
}
tick()
requestAnimationFrame(loop)
input[type="radio"] {
display: none;
}
label {
font-family: sans-serif;
padding: 10px;
margin: 10px;
display: block;
cursor: pointer;
border-radius: 5px;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
input[type="radio"]+label {
background-color: rgba(0,220,220,0);
transition: background-color .5s ease-out 0s;
}
input[type="radio"]:checked+label {
background-color: rgba(0,220,220,1);
}
#time {
width: 100%;
font-family: sans-serif;
font-size: 2em;
text-align: center;
}
<input type="radio" name="group1" id="london" checked>
<label for="london" onclick="setOffset(0)">London</label>
<input type="radio" name="group1" id="amsterdam">
<label for="amsterdam" onclick="setOffset(1)">Amsterdam</label>
<input type="radio" name="group1" id="new-york">
<label for="new-york" onclick="setOffset(-4)">New York</label>
<div id="time"></div>