From 29178f8373ac93c3b41c0e6d5c3d35b8809142a9 Mon Sep 17 00:00:00 2001 From: le2yunji Date: Fri, 26 Jun 2026 16:27:26 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=2007-Stopwatch=20=EA=B3=BC=EC=A0=9C?= =?UTF-8?q?=20=EC=A0=9C=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 07-Stopwatch/le2yunji/Stopwatch.tsx | 86 +++++++++++++++++++++++ 07-Stopwatch/le2yunji/style/stopwatch.css | 45 ++++++++++++ 07-Stopwatch/le2yunji/utils/formatTime.ts | 21 ++++++ 3 files changed, 152 insertions(+) create mode 100644 07-Stopwatch/le2yunji/Stopwatch.tsx create mode 100644 07-Stopwatch/le2yunji/style/stopwatch.css create mode 100644 07-Stopwatch/le2yunji/utils/formatTime.ts diff --git a/07-Stopwatch/le2yunji/Stopwatch.tsx b/07-Stopwatch/le2yunji/Stopwatch.tsx new file mode 100644 index 0000000..3414d2c --- /dev/null +++ b/07-Stopwatch/le2yunji/Stopwatch.tsx @@ -0,0 +1,86 @@ +import { useEffect, useState } from "react"; +import formatTime from "./utils/formatTime"; +import "./style/stopwatch.css"; + +export default function Stopwatch() { + const [startTime, setStartTime] = useState(null); + const [isRunning, setIsRunning] = useState(false); + const [elapsedTime, setElapsedTime] = useState(0); + const [now, setNow] = useState(null); + + useEffect(() => { + if (!isRunning) return; + + let animationFrameId: number; + + const update = () => { + setNow(performance.now()); + animationFrameId = requestAnimationFrame(update); + }; + + animationFrameId = requestAnimationFrame(update); + + return () => cancelAnimationFrame(animationFrameId); + }, [isRunning]); + + const displayTime = + isRunning && startTime !== null && now !== null + ? elapsedTime + (now - startTime) + : elapsedTime; + + const handleStopwatch = () => { + if (!isRunning) { + const currentTime = performance.now(); + + setStartTime(currentTime); + setNow(currentTime); + setIsRunning(true); + return; + } + + if (startTime === null || now === null) return; + + const currentTime = performance.now(); + + setElapsedTime((prev) => prev + currentTime - startTime); + setStartTime(null); + setNow(null); + setIsRunning(false); + }; + + const handleReset = () => { + setElapsedTime(0); + setNow(null); + setStartTime(null); + setIsRunning(false); + }; + + return ( +
+
{formatTime(displayTime)}
+
+ + +
+
+ ); +} + +// 시간을 어떻게 증가시키지 ? +// 경과 시간 = 현재 시각 - 누른 시간 + 이전에 누적된 시간 + +// Date.now()는 이런 곳에서 쓰면 돼. + +// 이벤트 핸들러 안 +// useEffect 안 +// setInterval / requestAnimationFrame 콜백 안 + +// 즉, 렌더 중에 직접 계산하지 말고, 현재 시간 값을 state로 저장해서 화면에 보여줘야 해. diff --git a/07-Stopwatch/le2yunji/style/stopwatch.css b/07-Stopwatch/le2yunji/style/stopwatch.css new file mode 100644 index 0000000..428ae05 --- /dev/null +++ b/07-Stopwatch/le2yunji/style/stopwatch.css @@ -0,0 +1,45 @@ +.stopwatch { + display: flex; + flex-direction: column; + align-items: center; + border-radius: 20px; + padding: 20px; + /* background-color: aqua; */ +} +.display { + font-variant-numeric: tabular-nums; + font-family: monospace; + font-size: 40px; + font-weight: 500; +} +button { + border: 0; + background: none; + padding: 0; + cursor: pointer; + font-size: 20px; +} +.button-set { + display: flex; + gap: 20px; + margin-top: 10px; +} +.button-stop { + border: 3px solid #ff303e; + padding: 10px; + border-radius: 10px; + background-color: #ff303e; + color: white; +} +.button-start { + border: 3px solid #2735ff; + padding: 10px; + border-radius: 10px; + background-color: #2735ff; + color: white; +} +.button-reset { + border: 1px solid; + padding: 5px; + border-radius: 10px; +} diff --git a/07-Stopwatch/le2yunji/utils/formatTime.ts b/07-Stopwatch/le2yunji/utils/formatTime.ts new file mode 100644 index 0000000..245b423 --- /dev/null +++ b/07-Stopwatch/le2yunji/utils/formatTime.ts @@ -0,0 +1,21 @@ +export default function formatTime(milliseconds: number) { + const hours = Math.floor(milliseconds / 1000 / 60 / 60); + const minutes = Math.floor((milliseconds / 1000 / 60) % 60); + const seconds = Math.floor((milliseconds / 1000) % 60); + const ms = Math.floor((milliseconds % 1000) / 10); + + const paddedMs = String(ms).padStart(2, "0"); + const paddedSeconds = String(seconds).padStart(2, "0"); + const paddedMinutes = String(minutes).padStart(2, "0"); + const paddedHours = String(hours).padStart(2, "0"); + + if (hours > 0) { + return `${paddedHours}h ${paddedMinutes}m ${paddedSeconds}s ${paddedMs}ms`; + } + + if (minutes > 0) { + return `${paddedMinutes}m ${paddedSeconds}s ${paddedMs}ms`; + } + + return `${paddedSeconds}s ${paddedMs}ms`; +} From d478b55a01208ca24783b25bad95123fbe2c9435 Mon Sep 17 00:00:00 2001 From: le2yunji Date: Fri, 26 Jun 2026 16:29:04 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Refac:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 07-Stopwatch/le2yunji/Stopwatch.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/07-Stopwatch/le2yunji/Stopwatch.tsx b/07-Stopwatch/le2yunji/Stopwatch.tsx index 3414d2c..5b19dbc 100644 --- a/07-Stopwatch/le2yunji/Stopwatch.tsx +++ b/07-Stopwatch/le2yunji/Stopwatch.tsx @@ -73,14 +73,3 @@ export default function Stopwatch() { ); } - -// 시간을 어떻게 증가시키지 ? -// 경과 시간 = 현재 시각 - 누른 시간 + 이전에 누적된 시간 - -// Date.now()는 이런 곳에서 쓰면 돼. - -// 이벤트 핸들러 안 -// useEffect 안 -// setInterval / requestAnimationFrame 콜백 안 - -// 즉, 렌더 중에 직접 계산하지 말고, 현재 시간 값을 state로 저장해서 화면에 보여줘야 해.