์น ์ ๋๋ฉ์ด์ ์ ์ฌ์ฉ์ ๊ฒฝํ(UX)์ ๊ฐํํ๋ ํต์ฌ ์์์ ๋๋ค. ์์ ๋ฒํผ ํธ๋ฒ ํจ๊ณผ๋ถํฐ ๋ฐ์ดํฐ ๊ธฐ๋ฐ ์๊ฐํ๊น์ง, ์ํฉ์ ๋ฐ๋ผ ์ด๋ค ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋๋์ ๋ฐ๋ผ ์ฑ๋ฅ๊ณผ ๊ฐ๋ ์ฑ์ด ํฌ๊ฒ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค. ์ด๋ฒ ๊ธ์์๋ Transition/Transform/Opacity, Keyframes, requestAnimationFrame(rAF), Web Animations API(WAAPI) ๋ค ๊ฐ์ง ๋ํ์ ์ธ ์ ๊ทผ๋ฒ์ ๋น๊ตํ๊ณ , ์ค์ ๋ก ์ธ์ ์ด๋ค ๊ฑธ ์ฐ๋ฉด ์ข์์ง ์ ๋ฆฌํด๋ณด๊ฒ ์ต๋๋ค.
1. CSS Transition + Transform / Opacity
https://developer.mozilla.org/ko/docs/Web/CSS/CSS_transitions/Using_CSS_transitions
CSS ํธ๋์ง์ ์ฌ์ฉํ๊ธฐ - CSS: Cascading Style Sheets | MDN
์น ์์ฑ์๋ ์ด๋ค ์์ฑ์ ์ด๋ค ๋ฐฉ์์ผ๋ก ์์ง์ผ์ง๋ฅผ ์ ์ํ ์ ์์ต๋๋ค. ์ด๊ฒ์ ๋ณต์กํ ํธ๋์ง์ ์ ์์ฑํ ์ ์๊ฒ ํฉ๋๋ค. ์ด๋ค ์์ฑ์ ์์ง์ด๊ฒ ํ๋ ๊ฒ์ด ๋ง์ด ๋์ง ์์ผ๋ฏ๋ก, ์ ๋๋ฉ์ด์
developer.mozilla.org
transition: <property> <duration> <timing-function> <delay>;
Transition์ ์ํ ๋ณํ(hover, active ๋ฑ)๋ฅผ ๋ถ๋๋ฝ๊ฒ ์ฐ๊ฒฐํด์ฃผ๋ ๋ฐ ํนํ๋์ด ์์ต๋๋ค. ํนํ transform๊ณผ opacity๋ GPU ํฉ์ฑ ๋จ๊ณ์์ ์ฒ๋ฆฌ๋๋ฏ๋ก ์ฑ๋ฅ์ ์ด์ ์ด ํฝ๋๋ค.
์๋ฅผ ๋ค์ด, ์ผํ๋ชฐ ์นด๋ hover ์ ํ๋๋๋ฉด์ ๊ทธ๋ฆผ์๊ฐ ๊ฐ์กฐ๋๊ฑฐ๋, ๋ฒํผ ํด๋ฆญ ์ ์ด์ง ํ์ด๋์ค๋ ํจ๊ณผ๋ transition์ผ๋ก ๊ฐ๋จํ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
button {
transition: transform 0.2s ease, opacity 0.2s ease;
}
button:hover {
transform: scale(1.05);
opacity: 0.9;
}
transition์ ํด๋น ์์ฑ ๊ฐ์ด ๋ฐ๋๋ ์๊ฐ์ ๋ฐ๋ํฉ๋๋ค.
์ฆ,
- DOM ์์์ ์คํ์ผ์ด ์ด๊ธฐ๊ฐ → ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ฐ๋๋ ์์
- ํน์ ํด๋์ค ํ ๊ธ/์คํ์ผ ์์ฑ ๋ณ๊ฒฝ์ผ๋ก CSS ์์ฑ์ด ๋ณํ ๋
์ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ ๊ฐ ์ฌ์ด๋ฅผ ๋ถ๋๋ฝ๊ฒ ๋ณด๊ฐํฉ๋๋ค.
=> ์ฆ, .class ์ถ๊ฐ/์ ๊ฑฐ, JS์์ style.height = ... ๊ฐ์ ๋ณ๊ฒฝ์ด ์ผ์ด๋ ์๊ฐ์ด ํธ๋ฆฌ๊ฑฐ๊ฐ ๋ฉ๋๋ค.
์์ ํ๋ฆ
.box {
height: 0;
transition: height 0.6s ease;
}
.box.expand {
height: 200px;
}
- ์ฒ์์ .box → height: 0
- JS์์ .expand ํด๋์ค๋ฅผ ์ถ๊ฐํ๋ฉด → height: 200px
- ์ด ์์ ์ transition์ด ๋ฐ๋ํ์ฌ 0 → 200px์ผ๋ก 0.6์ด ๋์ ๋ถ๋๋ฝ๊ฒ ๋ณํํฉ๋๋ค.
์ฃผ์ํ ์
- transition์ ์ด๊ธฐ ๋ ๋๋ง ์์๋ ๋ฐ๋ํ์ง ์์ต๋๋ค.
(DOM์ด ๊ทธ๋ ค์ง ๋ ์ด๋ฏธ ๊ฐ์ด ์ ํด์ ธ ์์ผ๋ฉด “๋ณํ”๊ฐ ์๋๋ฏ๋ก ์ ๋๋ฉ์ด์ ์์) - ๋ฐ๋์ ๊ฐ์ด ๋ฐ๋๋ ์๊ฐ์ด ์์ด์ผ ํฉ๋๋ค.
- display์ฒ๋ผ ์ ๋๋ฉ์ด์ ๋ถ๊ฐ๋ฅํ ์์ฑ์ transition์ด ๋ฐ๋ํ์ง ์์ต๋๋ค.
- height: auto → height: 0 ๊ฐ์ ๊ฒฝ์ฐ์ฒ๋ผ, ์ง์ ์ ๋๋ฉ์ด์ ํ ์ ์๋ ๊ฐ(auto)์ ๋ฐ๋ํ์ง ์์. → ๊ทธ๋์ ๋ณดํต scrollHeight(px)๋ก ๊ณ์ฐํ ๊ฐ์ ๋ฃ์ต๋๋ค.
2. CSS Keyframes Animation: ๋ฐ๋ณต ๋์๊ณผ ํจํด ์ ๋๋ฉ์ด์ ์ ์ต์ ํ
https://developer.mozilla.org/ko/docs/Web/CSS/@keyframes
@keyframes - CSS: Cascading Style Sheets | MDN
ํคํ๋ ์์ ์ฌ์ฉํ๋ ค๋ฉด @keyframes ๋ฃฐ์ ์ ๋๋ฉ์ด์ ๊ณผ ํคํ๋ ์ ๋ฆฌ์คํธ๋ฅผ ๋งค์นญ์ํฌ animation-name ์์ฑ์ผ๋ก ์ฌ์ฉํ ์ด๋ฆ๊ณผ ํจ๊ป ์์ฑํฉ๋๋ค. ๊ฐ @keyframes ๋ฃฐ์ ํคํ๋ ์ ์ ํ์์ ์คํ์ผ ๋ฆฌ์คํธ๋ฅผ ํฌ
developer.mozilla.org
Transition์ ํ ๋ฒ์ ์ํ ๋ณํ์ ์ ํฉํ์ง๋ง, ์ฃผ๊ธฐ์ ์ด๊ณ ๋ฐ๋ณต์ ์ธ ์ ๋๋ฉ์ด์ ์ด ํ์ํ ๋ Keyframes๊ฐ ๊ฐ๋ ฅํฉ๋๋ค. ๋ก๋ฉ ์คํผ๋, ๋ฐฐ๋ ๋กค๋ง, ์ฃผ๊ธฐ์ ๊ฐ์กฐ ํจ๊ณผ ๋ฑ์ ์ ํฉํฉ๋๋ค.
@keyframes scaleAnimation {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.loader {
width: 40px; height: 40px;
background: steelblue;
animation: scaleAnimation 2s ease-in-out infinite;
}
ํน์ง
- @keyframes ๊ท์น์ ์ฌ์ฉํ์ฌ ํน์ ์์ฑ์ด ์๊ฐ์ ๋ฐ๋ผ ์ด๋ป๊ฒ ๋ณํ๋์ง๋ฅผ ๋จ๊ณ๋ณ๋ก ์ ์ํ๋ ๋ฐฉ์์ ๋๋ค.
- ๋จ์ํ ํ ๋ฒ์ ์ํ ์ ํ์ ์ฒ๋ฆฌํ๋ transition๊ณผ ๋ฌ๋ฆฌ, ์ฌ๋ฌ ๋จ๊ณ๋ฅผ ์ ์ํ๊ฑฐ๋ ๋ฐ๋ณต(loop) ์ํฌ ์ ์์ต๋๋ค.
- CSS๋ง์ผ๋ก๋ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋๋ฉฐ, ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ์์ด๋ ์๋์ผ๋ก ์ ๋๋ฉ์ด์ ์ด ๋์ํฉ๋๋ค.
๋ฌธ๋ฒ
@keyframes myAnimation {
0% { transform: translateY(0); opacity: 0; }
50% { transform: translateY(-10px); opacity: 1; }
100% { transform: translateY(0); opacity: 1; }
}
.box {
animation: myAnimation 1s ease-in-out infinite;
}
- @keyframes → ์ ๋๋ฉ์ด์ ์ด๋ฆ ์ ์
- 0%, 50%, 100% → ์๊ฐ ์ถ์์ ์์ฑ ๊ฐ ๋ณํ ์ ์
- .box์ animation ์์ฑ ์ ์ฉ → ์ด๋ฆ/์๊ฐ/ํ์ด๋ฐ ํจ์/๋ฐ๋ณต ์ฌ๋ถ ์ค์
์์ฑ
- animation-name → @keyframes ์ด๋ฆ ์ง์
- animation-duration → ์ฌ์ ์๊ฐ (์: 2s)
- animation-timing-function → ์๋ ๊ณก์ (์: ease-in-out, cubic-bezier)
- animation-delay → ์์ ์ง์ฐ ์๊ฐ
- animation-iteration-count → ๋ฐ๋ณต ํ์ (์: infinite)
- animation-direction → normal, reverse, alternate ๋ฑ (์๋ณต ์ ๋๋ฉ์ด์ ๊ฐ๋ฅ)
- animation-fill-mode → ์์ ์ /๋๋ ํ ์ํ ์ ์ง ์ฌ๋ถ (forwards, backwards, both)
3. requestAnimationFrame (rAF): ์ ๊ตํ ์ ์ด๊ฐ ํ์ํ ๊ฒฝ์ฐ
CSS ์ ๋๋ฉ์ด์ ์ผ๋ก ํด๊ฒฐํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ, ํนํ ์คํฌ๋กค, ๋ฐ์ดํฐ ์๊ฐํ, ์ํธ์์ฉ ๊ธฐ๋ฐ ์ ๋๋ฉ์ด์ ์๋ requestAnimationFrame์ด ํ์ํฉ๋๋ค.
rAF๋ ๋ธ๋ผ์ฐ์ ์ ๋ฆฌํ์ธํธ ์ฃผ๊ธฐ(VSync)์ ๋ง์ถฐ ํธ์ถ๋๋ฏ๋ก, setInterval๋ณด๋ค ํจ์ฌ ๋ถ๋๋ฝ๊ณ ํจ์จ์ ์ ๋๋ค.
const box = document.getElementById('box');
let start = null;
function step(timestamp) {
if (!start) start = timestamp;
const progress = timestamp - start;
box.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`;
if (progress < 2000) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
ํ์ฉ ์์
- ํจ๋ด๋์ค ์คํฌ๋กค
- ์ฐจํธ ์ ์ง์ ๊ทธ๋ฆฌ๊ธฐ
- ์ง๋์์ ์์ฐ์ค๋ฌ์ด ์ด๋
4. Web Animations API (WAAPI): ์ ๋ฐ ์ ์ด๊ฐ ํ์ํ ๋
WAAPI๋ JS์์ CSS Keyframes์ฒ๋ผ ์ ๋๋ฉ์ด์ ์ ์ ์ํ๊ณ , pause/play/reverse/finish ๋ฑ ์ ๋ฐ ์ ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋์์ธ ์์คํ ์ด๋ ๋์๋ณด๋์ฒ๋ผ ์ ๋๋ฉ์ด์ ์ํ๋ฅผ ์ง์ ๊ด๋ฆฌํด์ผ ํ๋ ๊ฒฝ์ฐ ์ ํฉํฉ๋๋ค.
const el = document.getElementById('animatedElement');
const anim = el.animate(
[
{ transform: 'scale(1)' },
{ transform: 'scale(1.5)' }
],
{ duration: 1000, iterations: Infinity, direction: 'alternate' }
);
// ํ์ ์ ์ ์ด ๊ฐ๋ฅ
anim.pause();
anim.play();
์ฅ์
- ๋ฐํ์ ์ ์ด (์ค๊ฐ์ ๋ฉ์ถ๊ธฐ, ๋๊ฐ๊ธฐ, ์๋ ๋ณ๊ฒฝ)
- CSS + JS ํผํฉ ์ ๋๋ฉ์ด์ ์ ๋ต ์๋ฆฝ ๊ฐ๋ฅ
๊ฒฐ๋ก
- Transition/Transform/Opacity → UI ํผ๋๋ฐฑ (๋ฒํผ, hover, ๋ชจ๋ฌ ๋ฑ์ฅ)
- Keyframes → ๋ฐ๋ณต์ /์ง์์ ๋์ (์คํผ๋, ๋ฐฐ๋)
- requestAnimationFrame → ์ํธ์์ฉ/๋ฐ์ดํฐ ๊ธฐ๋ฐ ์ ๋๋ฉ์ด์ (์คํฌ๋กค, ์ฐจํธ)
- WAAPI → ์กฐ๊ฑด๋ถ/์ ๋ฐ ์ ์ด๊ฐ ํ์ํ ๊ฒฝ์ฐ (๋์๋ณด๋, ๋์์ธ ์์คํ )
์น ์ ๋๋ฉ์ด์ ์ “์์๊ฒ ๊พธ๋ฏธ๊ธฐ” ์์ค์ ๋์ด, ์ฌ์ฉ์์ ์์ ์ ์์ฐ์ค๋ฝ๊ฒ ์ด๋๊ณ ์ธํฐ๋์ ์ ์ง๊ด์ ์ผ๋ก ๋ง๋๋ ๋๊ตฌ์ ๋๋ค. ๋จ์ํ UI ํผ๋๋ฐฑ์ Transition/Keyframes๋ก, ๋ฐ์ดํฐ ๊ธฐ๋ฐ ํน์ ์กฐ๊ฑด ์ ์ด๊ฐ ํ์ํ๋ค๋ฉด rAF๋ WAAPI๋ก ์ ๊ทผํ ์ ์์ต๋๋ค.
๊ถ๊ทน์ ์ผ๋ก ์ค์ํ ๊ฑด ๋งฅ๋ฝ์ ๋ง๋ ๋๊ตฌ ์ ํ์ ๋๋ค. ๋ถํ์ํ ์ ๋๋ฉ์ด์ ์ ์คํ๋ ค UX๋ฅผ ํด์น ์ ์์ผ๋, “์ ์ด ์ ๋๋ฉ์ด์ ์ด ํ์ํ๊ฐ?”๋ฅผ ๋จผ์ ๊ณ ๋ฏผํ๋ ์ต๊ด์ด ํ์ํ๋ค๊ณ ๋๊ผ์ต๋๋ค
'๐ ํ๋ก ํธ์๋(FE)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| Next.js 16 + Turbopack + pnpm ๋ชจ๋ ธ๋ ํฌ: Docker ๋น๋ ์์ ์ ๋ณต (0) | 2026.01.17 |
|---|---|
| ์ง๋ ๋ทฐํฌํธ ๊ธฐ๋ฐ ์ค์๊ฐ ์์ ๊ฒ์ ๊ตฌํ (0) | 2026.01.14 |
| Webpack Tree Shaking ๋์ ์๋ฆฌ (0) | 2025.09.17 |
| Webpack + Babel + TypeScript ์ค์ ๊ฐ์ด๋ (0) | 2025.09.16 |
| MVC ๊ตฌ์กฐ - ์ต์ ๋ฒ ํจํด ( Observer Pattern ) (0) | 2025.09.08 |