← Назад к вопросам
Как реализовать секундомер?
2.0 Middle🔥 142 комментариев
#Soft Skills и рабочие процессы
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализовать секундомер
Базовая реализация с setInterval
Простейший вариант — использовать setInterval и счётчик времени:
class Stopwatch {
constructor() {
this.seconds = 0;
this.intervalId = null;
this.isRunning = false;
}
start() {
if (this.isRunning) return;
this.isRunning = true;
this.intervalId = setInterval(() => {
this.seconds++;
this.display();
}, 1000); // каждую секунду
}
stop() {
this.isRunning = false;
clearInterval(this.intervalId);
}
reset() {
this.seconds = 0;
this.stop();
this.display();
}
display() {
const hours = Math.floor(this.seconds / 3600);
const minutes = Math.floor((this.seconds % 3600) / 60);
const secs = this.seconds % 60;
const timeString = `${this.pad(hours)}:${this.pad(minutes)}:${this.pad(secs)}`;
document.getElementById('display').textContent = timeString;
}
pad(num) {
return num.toString().padStart(2, '0');
}
}
// Использование
const stopwatch = new Stopwatch();
document.getElementById('start').addEventListener('click', () => stopwatch.start());
document.getElementById('stop').addEventListener('click', () => stopwatch.stop());
document.getElementById('reset').addEventListener('click', () => stopwatch.reset());
Проблема: неточность setInterval
setInterval может отклоняться из-за браузера и системы:
// Проблема: setInterval может задерживаться
setInterval(() => {
seconds++; // Может быть не точно каждую секунду!
}, 1000);
// За 10 минут может ошибиться на несколько секунд
Лучший вариант: с performance.now()
Используй реальное время вместо счётчика:
class AccurateStopwatch {
constructor() {
this.startTime = null;
this.elapsedTime = 0; // миллисекунды
this.animationId = null;
this.isRunning = false;
}
start() {
if (this.isRunning) return;
this.isRunning = true;
this.startTime = performance.now();
const update = () => {
const currentTime = performance.now();
this.elapsedTime += currentTime - this.startTime;
this.startTime = currentTime;
this.display();
if (this.isRunning) {
this.animationId = requestAnimationFrame(update);
}
};
this.animationId = requestAnimationFrame(update);
}
stop() {
this.isRunning = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
reset() {
this.elapsedTime = 0;
this.stop();
this.display();
}
display() {
const totalSeconds = Math.floor(this.elapsedTime / 1000);
const milliseconds = Math.floor((this.elapsedTime % 1000) / 10); // сотые доли
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const timeString = `${this.pad(hours)}:${this.pad(minutes)}:${this.pad(seconds)}.${this.pad(milliseconds)}`;
document.getElementById('display').textContent = timeString;
}
pad(num) {
return num.toString().padStart(2, '0');
}
}
Этот вариант точнее, потому что использует реальное время браузера.
React компонент секундомера
import React, { useState, useRef } from 'react';
function Stopwatch() {
const [time, setTime] = useState(0); // миллисекунды
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(null);
const start = () => {
if (isRunning) return;
setIsRunning(true);
const startTime = Date.now() - time;
intervalRef.current = setInterval(() => {
setTime(Date.now() - startTime);
}, 10); // обновляем каждые 10ms для плавности
};
const stop = () => {
setIsRunning(false);
clearInterval(intervalRef.current);
};
const reset = () => {
setTime(0);
setIsRunning(false);
clearInterval(intervalRef.current);
};
const formatTime = (ms) => {
const totalSeconds = Math.floor(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const milliseconds = Math.floor((ms % 1000) / 10);
const pad = (num) => num.toString().padStart(2, '0');
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}.${pad(milliseconds)}`;
};
return (
<div className="stopwatch">
<div className="display">{formatTime(time)}</div>
<button onClick={start} disabled={isRunning}>
Start
</button>
<button onClick={stop} disabled={!isRunning}>
Stop
</button>
<button onClick={reset}>
Reset
</button>
</div>
);
}
export default Stopwatch;
С lap-функцией (как в Apple Watch)
class StopwatchWithLaps {
constructor() {
this.elapsedTime = 0;
this.laps = [];
this.lastLapTime = 0;
this.isRunning = false;
this.animationId = null;
}
start() {
if (this.isRunning) return;
this.isRunning = true;
const startTime = performance.now();
let lastFrameTime = startTime;
const update = (currentTime) => {
const deltaTime = currentTime - lastFrameTime;
this.elapsedTime += deltaTime;
lastFrameTime = currentTime;
this.display();
if (this.isRunning) {
this.animationId = requestAnimationFrame(update);
}
};
this.animationId = requestAnimationFrame(update);
}
stop() {
this.isRunning = false;
if (this.animationId) cancelAnimationFrame(this.animationId);
}
lap() {
if (!this.isRunning) return;
const lapTime = this.elapsedTime - this.lastLapTime;
this.laps.push({
lapNumber: this.laps.length + 1,
time: this.formatTime(lapTime),
totalTime: this.formatTime(this.elapsedTime)
});
this.lastLapTime = this.elapsedTime;
this.updateLapsList();
}
reset() {
this.elapsedTime = 0;
this.laps = [];
this.lastLapTime = 0;
this.stop();
this.display();
this.updateLapsList();
}
display() {
const timeString = this.formatTime(this.elapsedTime);
document.getElementById('display').textContent = timeString;
}
updateLapsList() {
const lapsList = document.getElementById('laps');
lapsList.innerHTML = this.laps
.map(
(lap) =>
`<div class="lap">
<span>Lap ${lap.lapNumber}: ${lap.time}</span>
<span>${lap.totalTime}</span>
</div>`
)
.join('');
}
formatTime(ms) {
const totalSeconds = Math.floor(ms / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const milliseconds = Math.floor((ms % 1000) / 10);
const pad = (num) => num.toString().padStart(2, '0');
return `${pad(minutes)}:${pad(seconds)}.${pad(milliseconds)}`;
}
}
Пример HTML
<div class="stopwatch-container">
<div class="display" id="display">00:00:00.00</div>
<div class="controls">
<button id="start" class="btn btn-primary">Start</button>
<button id="stop" class="btn btn-secondary" disabled>Stop</button>
<button id="lap" class="btn btn-tertiary" disabled>Lap</button>
<button id="reset" class="btn btn-danger">Reset</button>
</div>
<div class="laps" id="laps"></div>
</div>
CSS для красивого отображения
.stopwatch-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
padding: 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
max-width: 400px;
}
.display {
font-size: 48px;
font-weight: bold;
font-family: 'Monaco', monospace;
color: white;
letter-spacing: 4px;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
width: 100%;
}
.btn {
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary {
background: #4CAF50;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #45a049;
transform: translateY(-2px);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.laps {
width: 100%;
max-height: 200px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 12px;
}
.lap {
display: flex;
justify-content: space-between;
padding: 8px;
color: white;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
Важные моменты
- Используй performance.now() — точнее чем Date.now()
- Используй requestAnimationFrame — синхронизирует с монитором (60fps)
- Отключай интервалы — clearInterval, чтобы не было утечек памяти
- Обновляй каждые 10-50ms — для плавной визуализации
- Сохраняй в localStorage — чтобы секундомер работал при refresh
- Обработай pause/resume — не всегда reset после паузы
Bonus: сохранение в localStorage
save() {
localStorage.setItem('stopwatch', JSON.stringify({
elapsedTime: this.elapsedTime,
isRunning: this.isRunning,
timestamp: Date.now()
}));
}
restore() {
const saved = localStorage.getItem('stopwatch');
if (saved) {
const { elapsedTime, isRunning, timestamp } = JSON.parse(saved);
const timePassedSinceSave = Date.now() - timestamp;
this.elapsedTime = elapsedTime + (isRunning ? timePassedSinceSave : 0);
if (isRunning) this.start();
}
}
Так секундомер будет работать даже после перезагрузки страницы!