ํ ํ๋ก์ ํธ๋ฅผ ์์ํ๋ฉด์ Family Controls API๋ฅผ ์ ์ฒญํ๋ ๊ณผ์ ์์ ์ฐ๋ฆฌ ํ์ด ์ ์ํ๋ ค๋ ์ฑ์ ์น์ฌ์ดํธ ๋งํฌ๋ฅผ ์๊ตฌ๋ฐ์๋ค. ๊ทธ๋ฐ๋ฐ ์ด์ ๋ง ์์ํ ํ๋ก์ ํธ์์ ์น ์ฌ์ดํธ๋ ๋น์ฐํ ์์ง ์์๊ฐ. ์ด๋ป๊ฒ ํ ์ง ๊ณ ๋ฏผํ๋ค๊ฐ, 4์๊ฐ ์ ๋๋ฉด ์ถฉ๋ถํ ๋ฐฐํฌ๊น์ง ์งํ์ด ๊ฐ๋ฅํ ๊ฒ ๊ฐ์์ ์ง์ ์งํํ๋ ค๊ณ ํ๋ค. ํ์ง๋ง ํํฐ๋์ด ๋ ธ์ ์ผ๋ก ๊ฐ๋จํ ์ฒ๋ฆฌํ๋ผ๊ณ ์กฐ์ธํด์ฃผ์ ์ ๊ทธ๋ ๊ฒ ํ์ง๋ง, ๋ญ๊ฐ ์์ฌ์์ด ๋จ์๋ค. ๊ทธ๋์ ์ง์ ๋ง๋ค์ด๋ณด๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.
CRA๋ ์ฌ์ฅ๋์ผ๋ฏ๋ก Vite๋ฅผ ์ฌ์ฉํด React์ TypeScript๋ก ํ๋ก์ ํธ๋ฅผ ๋น๋ํด ์งํํ๋ค. ํจํค์ง ๋งค๋์ ๋ก๋ pnpm์ ์ฌ์ฉํ๋ค. ์ด๊ธฐ์๋ Swiper ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด ํ๋ฉด์ ์ฌ๋ผ์ด๋๋ก ๊ตฌํํ๋ ค๊ณ ํ์ผ๋, ์ต์ข ์ ์ผ๋ก๋ ํ์ด์ง ๋ผ์ฐํฐ๋ก ๊ตฌํํ๊ฒ ๋๋ค.
React, React-Dom, React-Router-Dom, Styled-Components๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋จํ๊ฒ ๊ตฌ์กฐ๋ฅผ ์ก์๋ค. iOS๋ฅผ ํ๋ฉด์ MVC์ MVVM ํจํด์ ์ฌ์ฉํ๋ ํฐ๋ผ, ์ค๋๋ง์ FE ๊ตฌ์กฐ๋ฅผ ๋๋๋ค๋ณด๋ ๋งค์ฐ ์ด์ํ๋ค. ์์ ๊ท๋ชจ์ ํ๋ก์ ํธ๋ผ ์ปดํฌ๋ํธ๋ฅผ ๋ง์ด ์ธ๋ถํํ์ง๋ ์์๊ณ , ํ์ด์ง๋ณ๋ก ๋๋๊ณ ๊ฐ๋ฐ ์งํ์ ํ๋ค. ๋ฉ์ธ ํ์ด์ง๋ ํ๋จ์ ์ฑ ๋ชฉ์ ์ ์ถ๊ฐํ ๊ฐ๋ฅ์ฑ์ด ์์ด ๋ณ๋๋ก ์น์ ์ปดํฌ๋ํธ๋ฅผ ๋๋์๋ค.
ํ๋ก์ ํธ ์ปจ์ ์ด STAR์ธ ๋งํผ ๋ฐคํ๋์ ๋ณ๊ณผ ์ ์ฑ ํจ๊ณผ๋ฅผ ๋ฃ๊ณ ์ถ์ด์ ์ด๋ฅผ ๊ตฌํํ๋ค.
MeteorEffect.tsx ์ปดํฌ๋ํธ์ ์ฑ ํน์ ๋ณ๋ฅ๋ณ ํจ๊ณผ๋ฅผ ์์ฑํ๋ ์ปดํฌ๋ํธ๋ค. ์ฃผ์ ๊ธฐ๋ฅ์ ๋ณ๋ฅ๋ณ์ฒ๋ผ ํ๋ฉด์ ๊ฐ๋ก์ง๋ฅด๋ ์์๋ค์ ๋๋คํ ์๋, ํฌ๊ธฐ, ์์์ผ๋ก ์์ฑํด ์ ๋๋ฉ์ด์ ํจ๊ณผ๋ฅผ ์ฃผ๋ ๊ฒ์ด๋ค.
import { useEffect, useState } from "react";
import styled, { createGlobalStyle, keyframes } from "styled-components";
// MeteorEffect ์ปดํฌ๋ํธ์ Props ํ์
์ ์
interface MeteorEffectProps {
count?: number; // ์ ์ฑ ๊ฐ์
maxDelay?: number; // ์ ๋๋ฉ์ด์
์์ ์ ์ต๋ ์ง์ฐ ์๊ฐ
minSpeed?: number; // ์ ์ฑ์ ์ต์ ์๋
maxSpeed?: number; // ์ ์ฑ์ ์ต๋ ์๋
angle?: number; // ์ ์ฑ์ด ๋จ์ด์ง๋ ๊ฐ๋
direction?: "left" | "right"; // ์ ์ฑ์ ๋จ์ด์ง๋ ๋ฐฉํฅ
}
const MAX_STAR_COUNT = 50; // ์ ์ฑ ๊ฐ์์ ์ต๋๊ฐ
const colors = ["#c77eff", "#f6ff7e", "#ff8d7e", "#ffffff"]; // ์ ์ฑ์ ์์ ๋ฐฐ์ด
const MeteorEffect = ({
count = 12, // ๊ธฐ๋ณธ ์ ์ฑ ๊ฐ์
maxDelay = 15, // ๊ธฐ๋ณธ ์ต๋ ์ง์ฐ ์๊ฐ
minSpeed = 2, // ๊ธฐ๋ณธ ์ต์ ์๋
maxSpeed = 4, // ๊ธฐ๋ณธ ์ต๋ ์๋
angle = 30, // ๊ธฐ๋ณธ ๊ฐ๋
direction = "right", // ๊ธฐ๋ณธ ๋ฐฉํฅ
}: MeteorEffectProps) => {
// ์ ์ฑ ๊ฐ์ ์ ํ
const starCount = count < MAX_STAR_COUNT ? count : MAX_STAR_COUNT;
// ์ ์ฑ ๊ฐ ๊ฐ๊ฒฉ ์ ์ฅ
const [starInterval, setStarInterval] = useState<number>(0);
// ์ฐฝ ํฌ๊ธฐ ๋ณํ์ ๋ฐ๋ผ ์ ์ฑ ๊ฐ ๊ฐ๊ฒฉ(starInterval)์ ๋์ ์ผ๋ก ๊ณ์ฐ
useEffect(() => {
const calcStarInterval = () => {
const innerWidth = window.innerWidth;
setStarInterval(Math.floor((innerWidth * 1.5) / (count * 5)));
};
calcStarInterval(); // ์ด๊ธฐ ๊ณ์ฐ
window.addEventListener("resize", calcStarInterval); // ์ฐฝ ํฌ๊ธฐ ๋ณ๊ฒฝ ์ด๋ฒคํธ ๋ฑ๋ก
return () => {
window.removeEventListener("resize", calcStarInterval); // ์ด๋ฒคํธ ํด์
};
}, [count]);
return (
<Wrapper> {/* ์ ์ฑ ํจ๊ณผ ์ ์ฒด๋ฅผ ๊ฐ์ธ๋ ๋ํผ */}
<GlobalMeteorStyle $direction={direction} $angle={angle} /> {/* ์ ์ญ ์คํ์ผ ์ ์ฉ */}
{new Array(starCount).fill(0).map((_, idx) => {
// ์ ์ฑ์ ์์น, ์ ๋๋ฉ์ด์
์์ฑ, ์์ ๋ฑ์ ๋๋ค์ผ๋ก ์ค์
const left =
direction === "left"
? `${Math.random() * count * 5 * starInterval}px`
: `${window.innerHeight - Math.random() * count * 5 * starInterval}px`;
const animationDelay = `${Math.random() * maxDelay}s`; // ์ง์ฐ ์๊ฐ
const animationDuration =
maxSpeed > minSpeed
? `${minSpeed + Math.random() * (maxSpeed - minSpeed)}s`
: `${2 + Math.random() * 4}s`; // ์ ๋๋ฉ์ด์
์ง์ ์๊ฐ
const colorIndex = Math.floor(Math.random() * colors.length); // ์์ ์ ํ
const size = `${2 + Math.floor(Math.random() * 5)}px`; // ์ ์ฑ ํฌ๊ธฐ
const boxShadow = `0px 0px 10px 3px ${colors[colorIndex]}`; // ์ ์ฑ ๋ฐ๊ด ํจ๊ณผ
return (
<div
key={idx}
style={{ left, animationDelay, animationDuration, boxShadow, width: size, height: size }}
className="star" // ์ ์ฑ ์คํ์ผ ์ง์
></div>
);
})}
</Wrapper>
);
};
// ์ ์ฑ์ด ๋จ์ด์ง๋ ์ ๋๋ฉ์ด์
์ ์
const MeteorKeyframe = (direction: "left" | "right", angle: number) => keyframes`
0% {
top: -10vh; /* ํ๋ฉด ์๋จ ๋ฐ์์ ์์ */
transform: translateX(0px); /* ์ด๊ธฐ ์์น */
opacity: 1; /* ์ด๊ธฐ ๋ถํฌ๋ช
๋ */
}
100% {
top: 110vh; /* ํ๋ฉด ํ๋จ ๋ฐ์ผ๋ก ์ด๋ */
transform: translateX(${direction === "left" ? "-" : "+"}${120 / Math.tan((angle * Math.PI) / 180)}vh);
opacity: 1; /* ๋ถํฌ๋ช
๋ ์ ์ง */
}
`;
// ์ ์ญ ์คํ์ผ ์ ์๋ฅผ ์ํ Props ํ์
interface GlobalMeteorProps {
$direction: "left" | "right";
$angle: number;
}
// ์ ์ฑ์ ์ ์ญ ์คํ์ผ ์ ์
const GlobalMeteorStyle = createGlobalStyle<GlobalMeteorProps>`
.star {
position: absolute;
top: -10%; /* ํ๋ฉด ์์ชฝ์์ ์์ */
left: 50%; /* ํ๋ฉด ์ค์ ๊ธฐ์ค */
width: 4px; /* ๊ธฐ๋ณธ ํฌ๊ธฐ */
height: 4px; /* ๊ธฐ๋ณธ ํฌ๊ธฐ */
border-radius: 50%; /* ์ํ */
background-color: white; /* ํฐ์ ๊ธฐ๋ณธ ์์ */
animation: ${(props) => MeteorKeyframe(props.$direction, props.$angle)} 4s ease-in infinite; /* ์ ๋๋ฉ์ด์
์ ์ฉ */
opacity: 0; /* ์ด๊ธฐ ๋ถํฌ๋ช
๋ */
}
.star::after {
position: absolute;
top: calc(50% - 1px); /* ์ค์ฌ์ถ ์ค์ */
left: -950%; /* ๊ผฌ๋ฆฌ ๊ธธ์ด */
width: 2000%; /* ๊ผฌ๋ฆฌ ๋๋น */
height: 2px; /* ๊ผฌ๋ฆฌ ๋๊ป */
background: linear-gradient(to left, #fff0, #ffffff); /* ๊ผฌ๋ฆฌ์ ๊ทธ๋ผ๋ฐ์ด์
*/
content: ""; /* ๊ฐ์ ์์ */
transform: ${(props) =>
props.$direction === "left"
? `rotateZ(-${props.$angle}deg)`
: `rotateZ(-${180 - props.$angle}deg)`} translateX(50%); /* ํ์ ๊ณผ ์์น ์กฐ์ */
}
`;
// ์ ์ฑ ํจ๊ณผ๋ฅผ ๊ฐ์ธ๋ Wrapper
const Wrapper = styled.div`
position: fixed; /* ํ๋ฉด ๊ณ ์ */
top: 0;
left: 0;
width: 100%; /* ์ ์ฒด ํ๋ฉด ๋๋น */
height: 100%; /* ์ ์ฒด ํ๋ฉด ๋์ด */
z-index: -10; /* ๋ฐฐ๊ฒฝ์ ์์น */
pointer-events: none; /* ์ํธ์์ฉ ๋นํ์ฑํ */
`;
export default MeteorEffect;
count: ์ ์ฑ์ ๊ฐ์๋ฅผ ๊ฒฐ์ maxDelay: ์ ๋๋ฉ์ด์
์ด ์์๋๊ธฐ ์ ์ง์ฐ ์๊ฐ์ ๋๋คํ๊ฒ ์ง์ minSpeed, maxSpeed: ์ ์ฑ์ ์ต์/์ต๋ ์๋๋ฅผ ์ค์ angle: ์ ์ฑ์ด ๋จ์ด์ง๋ ๊ฐ๋ (๊ธฐ๋ณธ๊ฐ: 30).direction: ์ ์ฑ์ด ๋จ์ด์ง๋ ๋ฐฉํฅ์ ์ค์ ("left" ๋๋ "right")useEffectstarInterval)์ ๋์ ์ผ๋ก ๊ณ์ฐํ์ฌ ๋ฐ์ํ ํจ๊ณผ ๋ง๋ ๋ค.MeteorKeyframekeyframesangle๊ณผ direction์ ์ฌ์ฉํ์ฌ ์ ์ฑ์ ์ด๋ ๊ฒฝ๋ก๋ฅผ ๊ณ์ฐGlobalMeteorStylebox-shadow๋ก ๋น๋๋ ํจ๊ณผ๋ฅผ ํํ, ::after๋ก ๊ธด ๊ผฌ๋ฆฌ ํจ๊ณผ๋ฅผ ์ถ๊ฐWrapperdiv, ํฌ์ธํฐ ์ด๋ฒคํธ๋ฅผ ๋นํ์ฑํ(pointer-events: none;)ํ์ฌ ์ธํฐํ์ด์ค์ ์ํฅ์ ์ฃผ์ง ์๋๋ค.count ๊ฐ์ ๋ฐ๋ผ ๊ฐ์๊ฐ ๊ฒฐ์ ๋๋ฉฐ, Math.random()์ ์ฌ์ฉํด ๋ค์์ ๋๋คํ๊ฒ ์ค์ ํ๋ค.left): ๋ฐฉํฅ์ ๋ฐ๋ผ ์์ ์์น๊ฐ ๋ค๋ฆ.animationDelay): ์ ์ฑ์ด ๋ํ๋๋ ๊ฐ๊ฒฉ.animationDuration): ์๋.size, boxShadow): ์ ์ฑ์ ํฌ๊ธฐ์ ๋น๊น.StarryNight.tsx ์ปดํฌ๋ํธ๋ณ ํจ๊ณผ๋ฅผ ์ํ StarryNight ์ปดํฌ๋ํธ๋ ๊ตฌํํ๋ค. ์ด ์ปดํฌ๋ํธ๋ ๋ฐ์ํ์ผ๋ก ์ฐฝ ํฌ๊ธฐ์ ๋ฐ๋ผ ๋ณ ๊ฐ์๋ฅผ ์กฐ์ ํ๋ค.
import { useEffect, useState } from "react";
import styled, { keyframes } from "styled-components";
const StarryNight = () => {
const [stars, setStars] = useState<JSX.Element[]>([]);
// ๋ณ์ ์์ฑํ์ฌ ํ๋ฉด์ ๋ ๋๋ง
const makeStars = () => {
const maxSize = Math.max(window.innerWidth, window.innerHeight); // ํ๋ฉด ํฌ๊ธฐ ๊ณ์ฐ
const starCount = Math.floor(maxSize / 2); // ๋ณ ๊ฐ์ ๊ฒฐ์
const getRandomX = () => Math.random() * maxSize; // ๋๋ค x์ขํ ์์ฑ
const getRandomY = () => Math.random() * maxSize; // ๋๋ค y์ขํ ์์ฑ
const randomRadius = () => Math.random() * 0.7 + 0.6; // ๋ณ ๋ฐ์ง๋ฆ ๋๋ค ์ค์
const newStars = Array.from({ length: starCount }, (_, index) => (
<Star key={index} cx={getRandomX()} cy={getRandomY()} r={randomRadius()} />
));
setStars(newStars);
};
useEffect(() => {
makeStars();
const handleResize = () => {
makeStars();
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return (
<BackSky>
<Sky>{stars}</Sky>
</BackSky>
);
};
// ๋ณ ํ๋ ํ์ ์ ๋๋ฉ์ด์
์ ์
const moveStar = keyframes`
from {
transform: translate(-50%, -50%) rotate(0);
}
to {
transform: translate(-50%, -50%) rotate(360deg);
}
`;
const BackSky = styled.div`
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: -1;
`;
const Sky = styled.svg`
width: 100vw;
height: 100vw;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: ${moveStar} 480s linear infinite;
`;
const Star = styled.circle`
fill: #fff;
`;
export default StarryNight;
makeStars)<circle> ์์๋ก ๊ตฌํwindow์ resize ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ์ฌ ํ๋ฉด ํฌ๊ธฐ๊ฐ ๋ณํ ๋ ๋ณ๋ค์ ๋ค์ ์์ฑmoveStar)makeStars๋ณ๋ค์ ์์ฑํ๊ณ useState๋ฅผ ํตํด ์ํ๋ก ๊ด๋ฆฌํ๋ค. ๋๋คํ ์ขํ์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง๋ ๋ณ๋ค๋ก ์ด๋ฃจ์ด์ง ๋ฐฐ์ด์ ์์ฑํ๋ค.
const makeStars = () => {
const maxSize = Math.max(window.innerWidth, window.innerHeight); // ๊ฐ์ฅ ํฐ ํ๋ฉด ํฌ๊ธฐ ๊ณ์ฐ
const starCount = Math.floor(maxSize / 2); // ๋ณ์ ๊ฐ์๋ ํ๋ฉด ํฌ๊ธฐ ๊ธฐ๋ฐ์ผ๋ก ๊ฒฐ์
const getRandomX = () => Math.random() * maxSize; // ๋๋ค X ์ขํ
const getRandomY = () => Math.random() * maxSize; // ๋๋ค Y ์ขํ
const randomRadius = () => Math.random() * 0.7 + 0.6; // ๋๋ค ๋ฐ์ง๋ฆ
const newStars = Array.from({ length: starCount }, (_, index) => (
<Star key={index} cx={getRandomX()} cy={getRandomY()} r={randomRadius()} />
));
setStars(newStars); // ์ํ ์
๋ฐ์ดํธ
};
useEffectuseEffect๋ ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ ๋๋ง๋ ๋ ๋ณ์ ์์ฑํ๋ฉฐ, resize ์ด๋ฒคํธ๋ฅผ ๋ฑ๋กํ์ฌ ํ๋ฉด ํฌ๊ธฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ ๋ณ์ ๋ค์ ์์ฑํ๋๋ก ํจ.
useEffect(() => {
makeStars();
const handleResize = () => {
makeStars(); // ํ๋ฉด ํฌ๊ธฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋ณ ๋ค์ ์์ฑ
};
window.addEventListener("resize", handleResize); // ์ด๋ฒคํธ ๋ฑ๋ก
return () => {
window.removeEventListener("resize", handleResize); // ์ด๋ฒคํธ ํด์
};
}, []);
BackSky: ๋ณ ํจ๊ณผ๊ฐ ๋ฐฐ๊ฒฝ์ผ๋ก ๋์ํ๋๋ก ํ๋ div. ์ ์ฒด ํ๋ฉด์ ๋ฎ๊ณ ๋ค๋ฅธ ์ฝํ
์ธ ์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ์ค์ Sky: ๋ณ๋ค์ด ํ์ ์ ๋๋ฉ์ด์
๊ณผ ํจ๊ป ๋ ๋๋ง๋๋ SVG ์์. transform ์์ฑ์ ์ฌ์ฉํ์ฌ ์ค์ ๊ธฐ์ค ํ์ ํจ๊ณผ๋ฅผ ์ ์ฉStar: ๊ฐ๋ณ ๋ณ์ ๋ํ๋ด๋ <circle>๋ก, ํฐ์์ผ๋ก ๋ ๋๋ง๋จmoveStar๋ ํ๋ ์ ์ฒด๊ฐ ํ์ ํ๋ ํจ๊ณผ๋ฅผ ๋ง๋ ๋ค. ์ ๋๋ฉ์ด์
์๋๋ 480์ด๋ก ์ค์ ํ์ฌ ๋งค์ฐ ์ฒ์ฒํ ์์ง์ด๋๋ก ํ๋ค.
const moveStar = keyframes`
from {
transform: translate(-50%, -50%) rotate(0); // ์ด๊ธฐ ์ํ
}
to {
transform: translate(-50%, -50%) rotate(360deg); // ํ ๋ฐํด ํ์
}
`;
App.tsx์์ MeteorEffect์ StarryNight๋ฅผ ์กฐํฉํ์ฌ ๋ฉ์ธ ํ์ด์ง๋ฅผ ๊ตฌ์ฑํ๋ค.
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Header from "./components/layout/Header";
import Home from "./pages/Home";
import Vision from "./pages/Vision";
import Feature from "./pages/Feature";
import Team from "./pages/Team";
import Footer from "./components/layout/Footer";
import { ThemeProvider } from "styled-components";
import GlobalStyle from "./styles/GlobalStyle";
import StarryNight from "./components/common/StarryNight";
import MeteorEffect from "./components/common/MeteorEffect";
import theme from "./styles/Theme";
function App() {
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Router>
<StarryNight />
<MeteorEffect count={20} angle={45} direction="right" />
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/vision" element={<Vision />} />
<Route path="/feature" element={<Feature />} />
<Route path="/team" element={<Team />} />
</Routes>
<Footer />
</Router>
</ThemeProvider>
);
}
export default App;
์ ์ฒด ๊ตฌ์กฐ์ ์คํ์ผ๋ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ThemeProvider์ GlobalStyle์ ์ ์ฉํด์ฃผ๊ณ ๋์์ธ ์์๋ฅผ importํ ๋ค Header์ Footer๊ฐ ํญ์ ๋ณด์ผ ์ ์๋๋ก ๊ทธ ์ฌ์ด์ ํ์ด์ง Routes๋ก ๊ตฌ์ฑํ ๊ฒ์ ์ ์ ์๋ค.

๋๋ค์ผ๋ก ๋จ์ด์ง๋ ์์ ์ ์ฑ๋ค๊ณผ, ์ฒ์ฒํ ๋์๊ฐ๋ ๋ฐคํ๋์ด ์๋ฆ๋ค์ด S T A R์ ์น ์ฌ์ดํธ๊ฐ ์์ฑ๋๋ค.
์ด ํ๋ก์ ํธ๋ ๊ฐ๋จํ ๋ง๋ค์์ง๋ง, ์ค์ ๋ฐฐํฌ์ ํ์ํ ์์ค์ผ๋ก ์ ์ํ๋ค. ํนํ, ์ ๋ก๋ฒ ์ด์ค์์ ์์ํด ๋์์ธ ์์์ ํจ๊ณผ์ ์ง์คํด ํ๋ก์ ํธ์ ์ปจ์ ์ ์๊ฐ์ ์ผ๋ก ์ ์ ๋ฌํ ์ ์๋๋ก ๋ ธ๋ ฅํ๋ค. API ์น์ธ ๋ฐ๊ณ ์ผ๋ฅธ ์ฑ๊น์ง ๊ตฌํ ํด๋ด๊ณ ์ถ๋ค.
์ ์ฑ ๋ฐฐ๊ฒฝํจ๊ณผ๋ฅผ ๋ง๋ค์ด ๋ณด๊ธฐ
[JS] ๋ฐคํ๋์ ๋ณ ๋ฐฐ๊ฒฝ ๋ง๋ค๊ธฐ