Iniciar sesión Registro
Anuncios
Tu espacio publicitario
Reserva este slot exclusivo para el periodo elegido.
Comprar publicidad →
Logotipo de la comunidad de telegram - Дивовижний світ веброзробки
Añadido 06 dic. 2025 🌐 UK

Дивовижний світ веброзробки

@babichdev
Número de suscriptores: 2 920
Fotos: 112
Videos: 7
Enlaces: 268
Descripción:
Дивовижний світ веброзробки — тепер і в твоєму телеграмі. Анонси відео з YouTube-каналу «Сергій Бабіч та Дивовижний світ веброзробки», стріми, авторські статті та цікаві знахідки. youtube.com/@babichweb Реклами та інтеграції обговоримо
Fuente

Дивовижний світ веброзробки | Днями на редіті побачив допис, в якому автор питав, чому його лічильни...

Logotipo de la comunidad de telegram - Дивовижний світ веброзробки Дивовижний світ веброзробки @babichdev
1 050 Vistas/Alcance 2026-09-25 07:24 Mensaje №447
Днями на редіті побачив допис, в якому автор питав, чому його лічильник часу на сторінці за пів години активності відстає аж на 30 секунд.

Задача, здавалось би, одна з найпростіших — запускай таймер, рахуй секунди, виводь і буде тобі щастя. Але річ в тому, що багато хто досі вважає таймери в JS точними.

На ділі що setTimeout, що setInterval не виконують ваш код через заданий проміжок часу. Вони ставлять його в чергу. Це суттєва різниця. Тобто це не "виконай через точно 1000ms", а радше "я тут вам задачку залишив, запусти за нагоди, але не раніше ніж 1000ms". Це якщо прям дуже приблизно. Я не буду вдаватися в деталі, там вам і улюблений event loop, і черги, і так звана однопоточність.

Давайте ліпше розберемо, як уникнути такого дрифта або хоча б зменшити відхилення до меж статистичної похибки.

Перше, що прийде нам в голову для такого лічильника, — запустити setInterval:
setInterval(() => doMagic(), 1000);

Це найнаївніший спосіб. setInterval намагається запускати callback із заданою періодичністю, але не гарантує момент його фактичного виконання. Якщо main thread зайнятий, виклик відбудеться пізніше. Якщо ж ми просто робимо seconds++, то починаємо вимірювати не час, а кількість викликів — і рано чи пізно ці дві величини можуть розійтися.

Кращим підходом буде запускати лічильник, коли попередня ітерація точно відпрацювала. Але просто переробляти на рекурсивний setTimeout не треба, бо він так само накопичуватиме затримку:
const tick = () => {
…
setTimeout(() => tick(), 1000);
}
tick();

Краще розраховувати наступний таймаут як залишок до бажаного моменту в майбутньому:
let next = performance.now() + 1000;
const tick = () => {
next += 1000;

const delay = Math.max(0, next - performance.now());
setTimeout(tick, delay);
}
setTimeout(tick, 1000);

Оцей delay буде трошки менше за 1000ms, бо враховує похибку попередньої ітерації.

Це працює в ідеальних умовах, коли основний потік нічим суттєво не зайнятий. Але setTimeout не гарантує, що callback виконається саме в заданий момент. Якщо main thread зайнятий JavaScript, layout, rendering чи іншою роботою, callback чекатиме. Якщо потік буде заблокований більше секунди, ми можемо взагалі проскочити момент оновлення інтерфейсу.

А оскільки нам зрештою треба не просто щось порахувати, а намалювати нове значення, можна передати останній крок requestAnimationFrame. Він не робить таймер точнішим і не обходить заблокований main thread — він просить браузер виконати оновлення перед наступним repaint.
let next = performance.now() + 1000;

const tick = () => {
console.log('tick');

next += 1000;

const delay = Math.max(0, next - performance.now());
setTimeout(() => requestAnimationFrame(tick), delay);
};

setTimeout(() => requestAnimationFrame(tick), 1000);

Тут requestAnimationFrame розвʼязує не проблему точності, а іншу: якщо результат треба показати користувачу, ми можемо синхронізувати DOM-update з наступним кадром браузера. Аби не перескакувати секунди, варто ще знати, скільки часу пройшло з останнього виконання:
let previous = performance.now();
let seconds = 0;

const tick = (now) => {
// Ось тут визначаємо, скільки реально минуло секунд
// і на скільки нам треба "стрибнути" вперед
const elapsed = now - previous;
previous = now;
seconds += elapsed / 1000;
…
setTimeout(() => requestAnimationFrame(tick), 1000);
};

requestAnimationFrame(tick);

Такий підхід дозволяє максимально точно рахувати "тіки". Але є одне але, якого ну ніяк не уникнути — якщо ваш UI таки важкий, то іноді лічильник буде прострибувати значення не через рівні проміжки часу, наприклад 0.5с > 1с > 3с. Але при цьому показуватиме точний час. Це вимушений трейдоф через завантажений основний потік. Але це вже проблема не лічильника, а ваша — бо чого це у вас така важка сторінка, га?

Що почитати:
MDN — setTimeout()
MDN — requestAnimationFrame()

Що почитати душнілам:
HTML Living Standard — Timers

---

Збір на авто для 21 ОМБр триває, зібрано 22 200грн з необхідних 271 500грн.

🔗 send.monobank.ua/jar/AeXQ6YRf2X
💳 5375411202918178