| ์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ | 
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 | 
| 9 | 10 | 11 | 12 | 13 | 14 | 15 | 
| 16 | 17 | 18 | 19 | 20 | 21 | 22 | 
| 23 | 24 | 25 | 26 | 27 | 28 | 29 | 
| 30 | 
- ํํธ๋ฆญ์ค
 - ๊ณจ๋
 - ๊ฐ์์์ด๋
 - Golden
 - ์ ๊ทผ์ฑ
 - ์นUI
 - ์ผ๋ฐํ
 - ์น์ ๊ทผ์ฑ
 - ํ๋ก ํธ์๋
 - ์ผ์ดํ๋ฐ๋ชฌํํฐ์ค
 - UIUX
 - ์น๊ฐ๋ฐ
 - js์คํฌ๋กค์ด๋ฒคํธ
 - ์คํฌ๋กค์ ๋๋ฉ์ด์ 
 - ์น๋์์ธ
 - kpopdemonhunters
 - ๋น๋ณด๋์ฐจํธ
 - ๋ทํ๋ฆญ์ค์ ๋๋ฉ์ด์ 
 - ๋ทํ๋ฆญ์คost
 - ๋ฐ๋ง์ผํ
 - ๋คํฌ๋ชจ๋ํ ๊ธ
 - ์๋ฐ์คํฌ๋ฆฝํธui
 - ์ฌ์๋ณด์ด์ฆ
 - JavaScript
 - ui์ปดํฌ๋ํธ
 - ์๋ฐ์คํฌ๋ฆฝํธ
 - ๋คํฌ๋ชจ๋
 - ๋ฐ๋๋ผ์คํฌ๋ฆฝํธ
 - UX๋์์ธ
 - ๋ฐ๋๋ผjs
 
- Today
 
- Total
 
UI Code Lab
๐ฏ Fullpage ์น์ฌ์ดํธ๋ฅผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ๊ตฌํํ๋ ๋ฐฉ๋ฒ – ์์ JavaScript๋ก ์คํํ๋ ์คํฌ๋กค ์ธํฐํ์ด์ค ๋ณธ๋ฌธ
๐ฏ Fullpage ์น์ฌ์ดํธ๋ฅผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ๊ตฌํํ๋ ๋ฐฉ๋ฒ – ์์ JavaScript๋ก ์คํํ๋ ์คํฌ๋กค ์ธํฐํ์ด์ค
๐ฏ๊ฟ์์ด๐ 2025. 7. 16. 08:34
โ ๊ฐ์
๋ง์ ์น์ฌ์ดํธ์์ ์ฌ์ฉ๋๋ ํํ์ด์ง(Fullpage) ์ธํฐํ์ด์ค๋
์ฌ์ฉ์ ๊ฒฝํ์ ์ง๊ด์ ์ผ๋ก ๋ง๋ค๊ณ , ์ฝํ ์ธ ์ ๋ชฐ์ ํ ์ ์๋ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ํ์ ์ผ๋ก fullPage.js์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋๋ฆฌ ์ฌ์ฉ๋์ง๋ง,
์์ JavaScript๋ง์ผ๋ก๋ ์ถฉ๋ถํ ๊ฐ๋ณ๊ณ ์ ์ฐํ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
์ด ๊ธ์์๋ ๋ฐ๋๋ผ JS ๊ธฐ๋ฐ์ fullpage ์น์ฌ์ดํธ UI๋ฅผ ์ง์  ๋ง๋๋ ๊ณผ์ ์
์์  ์ค์ฌ์ผ๋ก ์์ธํ ์๊ฐํฉ๋๋ค.
๐ฆ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด ์ง์  ๊ตฌํํ ๊น?
- ์ฑ๋ฅ ์ต์ ํ: ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ถํฌํจ์ผ๋ก ํ์ด์ง ๋ก๋ฉ ์๋ ๊ฐ์
 - ์ปค์คํฐ๋ง์ด์ง ์์ ๋: ๋ด๊ฐ ์ง์  ๋ง๋ ์ฝ๋๋ก ์ํ๋ ๋๋ก ์์  ๊ฐ๋ฅ
 - ํ์ต ํจ๊ณผ: ์คํฌ๋กค ์ด๋ฒคํธ์ UI ํ๋ฆ ์ดํด๋๊ฐ ๋์์ง
 
๐งฑ HTML ๋ฐ CSS ๊ธฐ๋ณธ ๊ตฌ์กฐ
<div class="section-container">
  <section class="section">Section 1</section>
  <section class="section">Section 2</section>
  <section class="section">Section 3</section>
</div>
html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
}
.section-container {
  transition: transform 0.7s ease;
}
.section {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 2rem;
}
โ๏ธ JavaScript๋ก ๋ถ๋๋ฌ์ด ์น์  ์ ํ ๊ตฌํํ๊ธฐ
const container = document.querySelector('.section-container');
let currentIndex = 0;
let isScrolling = false;
const sectionCount = document.querySelectorAll('.section').length;
window.addEventListener('wheel', (e) => {
  if (isScrolling) return;
  isScrolling = true;
  const direction = e.deltaY > 0 ? 1 : -1;
  currentIndex = Math.min(Math.max(currentIndex + direction, 0), sectionCount - 1);
  container.style.transform = `translateY(-${currentIndex * 100}vh)`;
  setTimeout(() => {
    isScrolling = false;
  }, 700);
});
๐๏ธ UX ํฅ์์ ์ํ ๊ณ ๊ธ ๊ธฐ๋ฅ ์์ด๋์ด
- Dot Navigation: ํ์ฌ ์์น๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ฌ์ด๋ ์ธ๋์ผ์ดํฐ
 - Mobile Touch Event ๋์: touchstart, touchend ์ด๋ฒคํธ๋ก ๋ชจ๋ฐ์ผ ์นํ UX
 - Active ํด๋์ค ๊ด๋ฆฌ: ํ์ฌ ์น์ ์๋ง .active ๋ถ์ฌํ์ฌ ์คํ์ผ ๋ถ๊ธฐ
 - requestAnimationFrame ๊ธฐ๋ฐ ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์ 
 
๐ฑ ๋ชจ๋ฐ์ผ ๋์ ์์ ์ฝ๋
let touchStartY = 0;
window.addEventListener('touchstart', (e) => {
  touchStartY = e.touches[0].clientY;
});
window.addEventListener('touchend', (e) => {
  const touchEndY = e.changedTouches[0].clientY;
  const deltaY = touchStartY - touchEndY;
  if (Math.abs(deltaY) < 50 || isScrolling) return;
  isScrolling = true;
  currentIndex = deltaY > 0
    ? Math.min(currentIndex + 1, sectionCount - 1)
    : Math.max(currentIndex - 1, 0);
  container.style.transform = `translateY(-${currentIndex * 100}vh)`;
  setTimeout(() => {
    isScrolling = false;
  }, 700);
});
๐ง Fullpage UI์ ๋์ ์๋ฆฌ – ์ฌ์ฉ์์ ์คํฌ๋กค ์ ์ดํ๊ธฐ
fullpage ์ธํฐํ์ด์ค๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ ๋ฒ์ ์คํฌ๋กค ์ ๋ ฅ์ ๋ฐ๋ผ
ํ๋ฉด ์ ์ฒด๊ฐ ํ ์น์  ๋จ์๋ก ์์ง์ด๋ ํํ์ผ.
์ด๊ฑธ ์ํด ๊ฐ์ฅ ํต์ฌ์ ์ผ๋ก ์ฒ๋ฆฌํด์ผ ํ ๊ฑด:
- ์ฌ์ฉ์์ ์คํฌ๋กค์ ๊ฐ์งํ๋ ๋ก์ง
 - ํ์ฌ ์น์ ์ ๊ธฐ์ค์ผ๋ก ๋ค์ ์น์ ์ผ๋ก ๋ถ๋๋ฝ๊ฒ ์ด๋
 - ์คํฌ๋กค ์ด๋ฒคํธ๊ฐ ๊ฒน์น์ง ์๋๋ก ์ ์ด(throttle ๋๋ flag ์ฒ๋ฆฌ)
 
let currentIndex = 0;
let isScrolling = false;
window.addEventListener('wheel', (e) => {
  if (isScrolling) return;
  isScrolling = true;
  const direction = e.deltaY > 0 ? 1 : -1;
  currentIndex = Math.min(Math.max(currentIndex + direction, 0), sectionCount - 1);
  container.style.transform = `translateY(-${currentIndex * 100}vh)`;
  setTimeout(() => isScrolling = false, 700); // ์ ํ ์๊ฐ๊ณผ ์ผ์น
});
โจ ์ฌ๊ธฐ์ ์ค์ํ ์ ์ isScrolling์ด๋ผ๋ ํ๋๊ทธ๋ฅผ ํ์ฉํด์ ์ฐ์ ์ ๋ ฅ์ ๋ง๋ ๊ฑฐ์ผ.
์ด ๋ฐฉ์์ Debounce๋ Throttle๋ณด๋ค ์ง๊ด์ ์ผ๋ก ๊ตฌํํ ์ ์๊ณ , UX ์์ ์ฑ์ ๋์ฌ์ค.
๐งฉ transition๊ณผ requestAnimationFrame ์ฐจ์ด์ 
๋๋ถ๋ถ CSS์์ transition: transform 0.7s ease;๋ฅผ ์ฌ์ฉํ์ง๋ง,
์ข ๋ ๋ฌผ๋ฆฌ์ ์ด๊ณ ๋ถ๋๋ฌ์ด ์์ง์์ ์ํ๋ค๋ฉด
requestAnimationFrame์ผ๋ก ์ปค์คํฐ๋ง์ด์งํ๋ ๋ฐฉ๋ฒ๋ ์์ด:
function smoothScroll(targetY) {
  const startY = container.getBoundingClientRect().top;
  const diff = targetY - startY;
  let start;
  function step(timestamp) {
    if (!start) start = timestamp;
    const progress = timestamp - start;
    const easing = Math.min(progress / 700, 1); // 700ms ๊ธฐ์ค
    container.style.transform = `translateY(${startY + diff * easing}px)`;
    if (easing < 1) requestAnimationFrame(step);
  }
  requestAnimationFrame(step);
}
๐ ์ด ๋ฐฉ์์ ์ฑ๋ฅ ์ ์ด๊ฐ ๋ ํ์ํ์ง๋ง,
๋ณต์กํ ์ ๋๋ฉ์ด์ ์ด๋ ์ปค์คํ  easing ์ปค๋ธ๋ฅผ ์ ์ฉํ๊ธฐ์ ์ ๋ฆฌํด.
๐ฑ ๋ชจ๋ฐ์ผ ํฐ์น ๋์์ ๋ฐ๋ฅธ UX ๊ฐ์
์คํฌ๋กค๊ณผ ํฐ์น๋ฅผ ๋ชจ๋ ์ง์ํ๋ ค๋ฉด ๋ฐ์คํฌํ๊ณผ ๋ชจ๋ฐ์ผ ์ด๋ฒคํธ๋ฅผ ๋ณํ ์ฒ๋ฆฌํด์ผ ํ๊ณ ,
๊ฐ๊ฐ์ ํน์ง์ ๋ฐ์ํด์ผ UX๊ฐ ๊นจ์ง์ง ์์:
- ํฐ์น๋ touchstart์ touchend๋ฅผ ์กฐํฉํด์ ์ฌ์ฉ์์ ์ค์์ดํ ๋ฐฉํฅ์ ํ๋จ
 - ํฐ์น ๊ฐ๋ ์กฐ์ ์ด UX์ ํฐ ์ํฅ์ ๋ฏธ์นจ
 - ์์ ์์ง์(delta < 50)์ ๋ฌด์ํ์ฌ ์ค์๋ ๋ฐฉ์ง
 
let touchStartY = 0;
window.addEventListener('touchstart', (e) => {
  touchStartY = e.touches[0].clientY;
});
window.addEventListener('touchend', (e) => {
  const touchEndY = e.changedTouches[0].clientY;
  const deltaY = touchStartY - touchEndY;
  if (Math.abs(deltaY) < 50 || isScrolling) return;
  isScrolling = true;
  currentIndex = deltaY > 0 ? Math.min(currentIndex + 1, sectionCount - 1) : Math.max(currentIndex - 1, 0);
  container.style.transform = `translateY(-${currentIndex * 100}vh)`;
  setTimeout(() => isScrolling = false, 700);
});
๐๏ธ Dot Navigation์ UX์  ๊ฐ์น
์  ๋ด๋น๊ฒ์ด์ ์ ์ฌ์ฉ์๊ฐ ํ์ฌ ์ด๋ ์น์ ์ ์๋์ง ์๊ฐ์ ์ผ๋ก ์ ์ ์๊ฒ ํด์ค.
๋ํ ํด๋ฆญ์ผ๋ก ํด๋น ์น์ ์ผ๋ก ์ ํํ ์ ์์ด:
<div class="dot-nav">
  <div class="dot active" data-index="0"></div>
  <div class="dot" data-index="1"></div>
  <div class="dot" data-index="2"></div>
</div>
document.querySelectorAll('.dot').forEach(dot => {
  dot.addEventListener('click', (e) => {
    currentIndex = Number(e.target.dataset.index);
    container.style.transform = `translateY(-${currentIndex * 100}vh)`;
    updateActiveDot();
  });
});
function updateActiveDot() {
  document.querySelectorAll('.dot').forEach(dot => dot.classList.remove('active'));
  document.querySelector(`.dot[data-index="${currentIndex}"]`).classList.add('active');
}
๐จ ๋ด๋น๊ฒ์ด์ ์ ์ ๊ทผ์ฑ, ์ฌ์ฉ์ฑ, ์๊ฐ์  ํผ๋๋ฐฑ๊น์ง
๋ชจ๋ ์ฑ๊ธธ ์ ์์ด์ “์์ง๋ง ํฐ” UX ์์์ผ.
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
ํํ์ด์ง ์น์ฌ์ดํธ๋ ์๊ฐ์ ์ผ๋ก ๊ฐ๋ ฌํ๊ณ
UX์ ๊ฐํ ์ธ์์ ๋จ๊ธธ ์ ์๋ UI ํจํด์ ๋๋ค.
๊ผญ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์์กดํ์ง ์์๋
์ง์  ๊ตฌํํ๋ฉด์ ํ์ต๊ณผ ํจ์จ์ ๋์์ ์ก์ ์ ์์ต๋๋ค.
์์ผ๋ก ์ด ๋ฐฉ์์ ํ์ฅํด ๋ชจ๋ฌ, ์ฌ๋ผ์ด๋, ๋ด๋น๊ฒ์ด์  ๋ฑ
๋ค๋ฅธ ์ปดํฌ๋ํธ์๋ ์ฐ๊ฒฐํด๋ณด์ธ์!