When the visitor's clock is wrong
Drag a browser clock twenty minutes out and watch one countdown end early while the one following the server does not.
<div class="demo demo--wide">
<div class="pair">
<div class="side">
<p class="demo-hint">Trusting the visitor's clock</p>
<div id="naive"></div>
</div>
<div class="side">
<p class="demo-hint">Following the server</p>
<div id="synced"></div>
</div>
</div>
<div class="demo-row">
<label class="demo-hint" for="skew">This browser's clock is</label>
<input class="slider" id="skew" type="range" min="-30" max="30" step="1" value="0">
<output class="demo-chip" id="skewOut">correct</output>
</div>
<dl class="demo-kv" id="out"></dl>
<p class="demo-hint">Both count to the same instant. The left one asks this machine what time it is;
the right one was told the server's time once, at render, and has used the offset ever since.</p>
</div>
import { createCountdownWidget, serverClock } from 'https://www.devix.pk/cdn/oss/countdown@1.0.0/index.js';
const out = document.getElementById('out');
const skew = document.getElementById('skew');
const skewOut = document.getElementById('skewOut');
// The sale really ends 90 seconds from now, by the server's reckoning.
const serverNow = Date.now();
const deadline = serverNow + 90 * 1000;
let offset = 0;
// Pretend this machine's clock is wrong, which is the whole point of the demo.
const browserClock = () => Date.now() + offset;
const naive = createCountdownWidget(document.getElementById('naive'), {
deadline,
now: browserClock,
variant: 'clock',
doneText: 'Sale over',
});
const synced = createCountdownWidget(document.getElementById('synced'), {
deadline,
// Told the server's time once, at render; the offset is worked out from it.
now: serverClock(new Date(serverNow).toISOString(), browserClock()),
variant: 'clock',
doneText: 'Sale over',
onTick: (parts, instance) => show(instance),
});
function show(instance = synced) {
const a = naive.read();
const b = instance.read();
out.innerHTML = `
<div><dt>this machine says</dt><dd>${new Date(browserClock()).toLocaleTimeString()}</dd></div>
<div><dt>the server says</dt><dd>${new Date(browserClock() - offset).toLocaleTimeString()}</dd></div>
<div><dt>trusting the browser</dt><dd>${a.done ? 'already over' : `${a.minutes}m ${a.seconds}s`}</dd></div>
<div><dt>following the server</dt><dd>${b.done ? 'over' : `${b.minutes}m ${b.seconds}s`}</dd></div>`;
}
skew.addEventListener('input', () => {
const minutes = Number(skew.value);
offset = minutes * 60 * 1000;
skewOut.textContent = minutes === 0 ? 'correct' : `${Math.abs(minutes)} minutes ${minutes > 0 ? 'fast' : 'slow'}`;
show();
});
show();
@import url('https://www.devix.pk/cdn/oss/countdown@1.0.0/countdown.min.css');
.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.side {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
padding: 14px 10px;
border: 1px solid var(--demo-line);
border-radius: 12px;
background: var(--demo-surface);
}
.slider { flex: 1; min-width: 140px; accent-color: var(--demo-accent); }
.demo-kv dd { font-variant-numeric: tabular-nums; }
@media (max-width: 420px) { .pair { grid-template-columns: 1fr; } }