본문 바로가기
HTML/CSS

[HTML/CSS/JS] 웹페이지 스탑워치, 타이머 만들기

by teamnova 2024. 12. 7.
728x90

JavaScript의 시간 관련 API와 상태 관리를 활용하여 타이머와 스톱워치를 만들어보도록 하겠습니다.

 

html:

먼저 타이머와 스톱워치의 기본 구조를 HTML로 작성하겠습니다.
각 섹션은 시간을 표시할 디스플레이와 제어 버튼들로 구성됩니다.

css: 

사용자가 직관적으로 사용할 수 있도록 
깔끔하고 모던한 디자인을 CSS로 구현하겠습니다.

js:

타이머와 스톱워치의 기능을 클래스로 구현하여
효율적으로 상태를 관리하고 코드를 구조화하겠습니다.

Date.now()를 사용하여 정확한 시간을 계산,
setInterval로 실시간 업데이트를 구현해보겠습니다.

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>타이머 & 스톱워치</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: #f0f0f0;
        }

        .container {
            display: flex;
            gap: 30px;
            padding: 20px;
        }

        .timer-section,
        .stopwatch-section {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            text-align: center;
        }

        .display {
            font-size: 3em;
            font-weight: bold;
            margin: 20px 0;
            font-family: monospace;
        }

        .controls {
            display: flex;
            gap: 10px;
            justify-content: center;
        }

        button {
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
            transition: all 0.3s ease;
        }

        button:hover {
            opacity: 0.8;
        }

        .start {
            background: #4CAF50;
            color: white;
        }

        .stop {
            background: #f44336;
            color: white;
        }

        .reset {
            background: #2196F3;
            color: white;
        }

        .timer-input {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            justify-content: center;
        }

        .timer-input input {
            width: 60px;
            padding: 5px;
            text-align: center;
            font-size: 1.2em;
        }

        h2 {
            color: #333;
            margin-bottom: 20px;
        }

        .lap-times {
            margin-top: 20px;
            max-height: 150px;
            overflow-y: auto;
            text-align: left;
        }

        .lap-time {
            padding: 5px;
            border-bottom: 1px solid #eee;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 타이머 섹션 -->
        <div class="timer-section">
            <h2>타이머</h2>
            <div class="timer-input">
                <input type="number" id="hours" placeholder="시" min="0" max="99">
                <input type="number" id="minutes" placeholder="분" min="0" max="59">
                <input type="number" id="seconds" placeholder="초" min="0" max="59">
            </div>
            <div class="display" id="timer-display">00:00:00</div>
            <div class="controls">
                <button class="start" id="timer-start">시작</button>
                <button class="stop" id="timer-stop">정지</button>
                <button class="reset" id="timer-reset">리셋</button>
            </div>
        </div>

        <!-- 스톱워치 섹션 -->
        <div class="stopwatch-section">
            <h2>스톱워치</h2>
            <div class="display" id="stopwatch-display">00:00:00</div>
            <div class="controls">
                <button class="start" id="stopwatch-start">시작</button>
                <button class="stop" id="stopwatch-stop">정지</button>
                <button class="reset" id="stopwatch-reset">리셋</button>
                <button class="start" id="stopwatch-lap">랩</button>
            </div>
            <div class="lap-times" id="lap-times"></div>
        </div>
    </div>

    <script>
        // 타이머 클래스
        class Timer {
            constructor(display) {
                this.display = display;
                this.interval = null;
                this.remainingSeconds = 0;
                this.isRunning = false;
            }

            start(hours, minutes, seconds) {
                if (this.isRunning) return;

                this.remainingSeconds = hours * 3600 + minutes * 60 + seconds;
                if (this.remainingSeconds <= 0) return;

                this.isRunning = true;
                this.interval = setInterval(() => {
                    this.remainingSeconds--;
                    this.updateDisplay();

                    if (this.remainingSeconds <= 0) {
                        this.stop();
                        alert('타이머가 종료되었습니다!');
                    }
                }, 1000);
            }

            stop() {
                clearInterval(this.interval);
                this.isRunning = false;
            }

            reset() {
                this.stop();
                this.remainingSeconds = 0;
                this.updateDisplay();
            }

            updateDisplay() {
                const hours = Math.floor(this.remainingSeconds / 3600);
                const minutes = Math.floor((this.remainingSeconds % 3600) / 60);
                const seconds = this.remainingSeconds % 60;

                this.display.textContent = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
            }
        }

        // 스톱워치 클래스
        class Stopwatch {
            constructor(display, lapTimesContainer) {
                this.display = display;
                this.lapTimesContainer = lapTimesContainer;
                this.interval = null;
                this.startTime = 0;
                this.elapsedTime = 0;
                this.isRunning = false;
                this.lapTimes = [];
            }

            start() {
                if (this.isRunning) return;

                this.isRunning = true;
                this.startTime = Date.now() - this.elapsedTime;
                this.interval = setInterval(() => this.updateDisplay(), 10);
            }

            stop() {
                if (!this.isRunning) return;

                clearInterval(this.interval);
                this.elapsedTime = Date.now() - this.startTime;
                this.isRunning = false;
            }

            reset() {
                this.stop();
                this.elapsedTime = 0;
                this.updateDisplay();
                this.lapTimes = [];
                this.lapTimesContainer.innerHTML = '';
            }

            lap() {
                if (!this.isRunning) return;

                const lapTime = this.formatTime(this.elapsedTime);
                this.lapTimes.push(lapTime);

                const lapElement = document.createElement('div');
                lapElement.className = 'lap-time';
                lapElement.textContent = `랩 ${this.lapTimes.length}: ${lapTime}`;
                this.lapTimesContainer.insertBefore(lapElement, this.lapTimesContainer.firstChild);
            }

            updateDisplay() {
                const currentTime = this.isRunning ? Date.now() - this.startTime : this.elapsedTime;
                this.display.textContent = this.formatTime(currentTime);
            }

            formatTime(ms) {
                const hours = Math.floor(ms / 3600000);
                const minutes = Math.floor((ms % 3600000) / 60000);
                const seconds = Math.floor((ms % 60000) / 1000);
                return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
            }
        }

        // 타이머 초기화
        const timer = new Timer(document.getElementById('timer-display'));
        document.getElementById('timer-start').addEventListener('click', () => {
            const hours = parseInt(document.getElementById('hours').value) || 0;
            const minutes = parseInt(document.getElementById('minutes').value) || 0;
            const seconds = parseInt(document.getElementById('seconds').value) || 0;
            timer.start(hours, minutes, seconds);
        });
        document.getElementById('timer-stop').addEventListener('click', () => timer.stop());
        document.getElementById('timer-reset').addEventListener('click', () => timer.reset());

        // 스톱워치 초기화
        const stopwatch = new Stopwatch(
            document.getElementById('stopwatch-display'),
            document.getElementById('lap-times')
        );
        document.getElementById('stopwatch-start').addEventListener('click', () => stopwatch.start());
        document.getElementById('stopwatch-stop').addEventListener('click', () => stopwatch.stop());
        document.getElementById('stopwatch-reset').addEventListener('click', () => stopwatch.reset());
        document.getElementById('stopwatch-lap').addEventListener('click', () => stopwatch.lap());
    </script>
</body>

</html>

 

시연영상