/
kochkatech
/
CorpGame
Обзор
Документация
Войти
/
kochkatech
/
CorpGame
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
master
frontend/src/components/Timer.tsx
44 строки
2 KB
kochkareal
chore: добавлен .gitattributes, нормализованы переносы строк (LF везде, кроме .bat/.cmd)
06 авг 2026, 15:18
06 авг 2026, 15:18
c25ef1b
Код
Авторство
О чём код?
import { useEffect, useRef, useState } from 'react'; /** * Обратный отсчёт до абсолютной цели (Date.now() + remainingSec), а не декремент счётчика — * не копит дрейф setInterval. Сервер каждые ~2с шлёт свежий remainingSec (time:sync), поэтому * все клиенты выравниваются по серверу. На паузе (running=false) отсчёт заморожен. */ export function Timer({ remainingSec, running = true }: { remainingSec: number; running?: boolean }) { const [display, setDisplay] = useState(remainingSec); const targetRef = useRef(Date.now() + remainingSec * 1000); useEffect(() => { targetRef.current = Date.now() + remainingSec * 1000; setDisplay(remainingSec); }, [remainingSec]); useEffect(() => { if (!running) { setDisplay(remainingSec); return; } const recompute = () => setDisplay(Math.max(0, Math.round((targetRef.current - Date.now()) / 1000))); const id = setInterval(recompute, 250); // При возврате на вкладку (alt-tab) браузер тормозит setInterval — пересчитываем сразу от // абсолютной цели, поэтому после длительного фона время сразу верное, а не «застывшее». const onVisible = () => { if (!document.hidden) recompute(); }; document.addEventListener('visibilitychange', onVisible); return () => { clearInterval(id); document.removeEventListener('visibilitychange', onVisible); }; }, [running, remainingSec]); const m = Math.floor(display / 60) .toString() .padStart(2, '0'); const s = Math.floor(display % 60) .toString() .padStart(2, '0'); return <span className="font-mono text-xl font-bold text-focus">{m}:{s}</span>; }