two-dimension-scroll
Version:
A smooth scroll library that detects both horizontal and vertical scroll and converts them to vertical smooth scrolling
1,378 lines (1,125 loc) โข 35.3 kB
Markdown
# TwoDimensionScroll ๐ฑ๐ฑ๏ธ
๊ฐ๋ก์ ์ธ๋ก ์คํฌ๋กค์ ๋ชจ๋ ๊ฐ์งํ์ฌ ๋ถ๋๋ฌ์ด ์ธ๋ก ์คํฌ๋กค๋ก ๋ณํํ๋ ํ์ ์ ์ธ JavaScript ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.
## โจ ์ฃผ์ ๊ธฐ๋ฅ
- **๐ฏ 2์ฐจ์ ์คํฌ๋กค ๊ฐ์ง**: ํธ๋ํจ๋๋ ๋งค์ง๋ง์ฐ์ค์ ๊ฐ๋ก ์คํฌ๋กค์ ์๋์ผ๋ก ์ธ๋ก ์คํฌ๋กค๋ก ๋ณํ
- **๐ ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์
**: requestAnimationFrame์ ์ฌ์ฉํ 60fps ๋ถ๋๋ฌ์ด ์คํฌ๋กค
- **๐ฑ ๋ชจ๋ฐ์ผ ์ต์ ํ**: ํ๊ฒฝ๋ณ ์ต์ ํ๋ ์คํฌ๋กค ๊ฒฝํ (PC/๋ชจ๋ฐ์ผ/ํ๋ธ๋ฆฟ)
- **โ๏ธ React ์๋ฒฝ ์ง์**: ๊ณต์ React Hook๊ณผ TypeScript ํ์
์ ์ ์ ๊ณต
- **๐ญ ๋ชจ๋ฌ ์นํ์ **: ๋ชจ๋ฌ ์ํ์์ ์๋ฒฝํ ์คํฌ๋กค ์ ์ด (๋ฐ๋ ์ฐจ๋จ + ๋ด๋ถ ํ์ฉ)
- **โฟ ์ ๊ทผ์ฑ ์ง์**: `prefers-reduced-motion`, ์คํฌ๋ฆฐ ๋ฆฌ๋, ํค๋ณด๋ ๋ค๋น๊ฒ์ด์
์๋ฒฝ ์ง์
- **๐๏ธ ๋ค์ํ ์ต์
**: ๊ฐ๋, ์ง์์๊ฐ, ์ด์ง ํจ์ ๋ฑ ์ธ๋ฐํ ์ปค์คํฐ๋ง์ด์ง
- **โก ๊ณ ์ฑ๋ฅ**: ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ด๊ณ ๋ฐฐํฐ๋ฆฌ ์นํ์ ์ธ ๊ตฌ์กฐ
- **๐ง TypeScript ์ง์**: ์์ ํ ํ์
์ ์ ํฌํจ
## ๐ ์์ํ๊ธฐ
### ์ค์น
```bash
npm install two-dimension-scroll
```
### Vanilla JavaScript ์ฌ์ฉ๋ฒ
```javascript
// ES Modules (๊ถ์ฅ)
import TwoDimensionScroll from 'two-dimension-scroll';
// CommonJS
const TwoDimensionScroll = require('two-dimension-scroll').default;
// ๊ธฐ๋ณธ ์ค์ ์ผ๋ก ์ด๊ธฐํ
const scroll = new TwoDimensionScroll();
// ์ปค์คํ
์ต์
์ผ๋ก ์ด๊ธฐํ
const scroll = new TwoDimensionScroll({
duration: 1200,
horizontalSensitivity: 1.5,
verticalSensitivity: 1.0,
debug: true
});
```
### React Hook ์ฌ์ฉ๋ฒ
React์์๋ **๋ ๊ฐ์ง ๋ฐฉ๋ฒ**์ ์ง์ํฉ๋๋ค:
> โ ๏ธ **Vite/Webpack ํ๊ฒฝ**: ๋์ require ๋ฏธ์ง์์ผ๋ก **๋ฐฉ๋ฒ 2 (ScrollClass ์ง์ ์ ๋ฌ) ํ์**
> ๐ก **Create React App**: ๋ฐฉ๋ฒ 1, 2 ๋ชจ๋ ์ง์
> ๐ง **Next.js/SSR**: ๋ฐฉ๋ฒ 2 ๊ถ์ฅ
#### ๋ฐฉ๋ฒ 1: ๊ฐ๋จํ ์ฌ์ฉ (์๋ ๊ฐ์ง) โ ๏ธ Vite ๋ฏธ์ง์
```tsx
import { useTwoDimensionScroll } from 'two-dimension-scroll/react';
function App() {
const { isReady, scrollPosition, scrollTo, scrollInfo } = useTwoDimensionScroll({
duration: 1200,
desktop: { lerp: 0.1, sensitivity: 1.2 },
mobile: { lerp: 0.15, sensitivity: 0.8 }
});
// ScrollClass ์ ๋ฌ ์์ด๋ ์๋์ผ๋ก ํด๋์ค๋ฅผ ์ฐพ์์ ์๋ํฉ๋๋ค!
// โ ๏ธ ์ฃผ์: Vite, Webpack ํ๊ฒฝ์์๋ ์๋ํ์ง ์์ต๋๋ค.
if (!isReady) return <div>Loading...</div>;
```
#### ๋ฐฉ๋ฒ 2: ๋ช
์์ ์ ๋ฌ (๊ถ์ฅ, โ
Vite ํธํ)
```tsx
import TwoDimensionScroll from 'two-dimension-scroll';
import { useTwoDimensionScroll } from 'two-dimension-scroll/react';
function App() {
const { isReady, scrollPosition, scrollTo, scrollInfo } = useTwoDimensionScroll(
{
duration: 1200,
desktop: { lerp: 0.1, sensitivity: 1.2 },
mobile: { lerp: 0.15, sensitivity: 0.8 }
},
{ ScrollClass: TwoDimensionScroll } // ํด๋์ค ์ง์ ์ ๋ฌ๋ก ์ต๋ ์์ ์ฑ ๋ณด์ฅ
);
if (!isReady) return <div>Loading...</div>;
return (
<div>
<p>ํ์ฌ ์์น: {scrollPosition}px</p>
<button onClick={() => scrollTo(0)}>๋งจ ์๋ก</button>
</div>
);
}
```
### CDN ์ฌ์ฉ
```html
<script src="path/to/dist/bundle-simple.js"></script>
<script>
const scroll = new TwoDimensionScroll({
duration: 1000,
debug: true
});
</script>
```
## โ๏ธ React์์ ์ฌ์ฉํ๊ธฐ
TwoDimensionScroll์ React ํ๊ฒฝ์ ์๋ฒฝํ๊ฒ ์ง์ํ์ฌ ์ต๊ณ ์ ๊ฐ๋ฐ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
### React ์ค์น ๋ฐ ์ค์
```bash
# npm์ผ๋ก ์ค์น
npm install two-dimension-scroll
# ๋๋ yarn์ผ๋ก ์ค์น
yarn add two-dimension-scroll
```
```tsx
// React Hook import
import {
useTwoDimensionScroll,
useModalScroll,
useScrollToTop,
useScrollProgress
} from 'two-dimension-scroll/react';
```
### ๊ธฐ๋ณธ React Hook ์ฌ์ฉ๋ฒ
```tsx
import React from 'react';
import { useTwoDimensionScroll } from 'two-dimension-scroll/react';
function App() {
const {
isReady,
scrollPosition,
scrollTo,
scrollInfo
} = useTwoDimensionScroll({
debug: process.env.NODE_ENV === 'development',
desktop: {
duration: 1000,
lerp: 0.1
},
mobile: {
duration: 800,
lerp: 0.15
}
});
if (!isReady) {
return <div>Loading scroll system...</div>;
}
return (
<div>
<div style={{ height: '200vh' }}>
<h1>Smooth Scroll with React</h1>
{/* ์คํฌ๋กค ์ ๋ณด ํ์ */}
<div style={{ position: 'fixed', top: 10, right: 10 }}>
<p>์์น: {Math.round(scrollPosition)}px</p>
<p>์งํ๋ฅ : {Math.round((scrollInfo?.progress || 0) * 100)}%</p>
</div>
{/* ์คํฌ๋กค ์ ์ด ๋ฒํผ */}
<button onClick={() => scrollTo(0, 1000)}>
๋งจ ์๋ก
</button>
<button onClick={() => scrollTo(scrollInfo?.maxPosition || 0)}>
๋งจ ์๋๋ก
</button>
</div>
</div>
);
}
```
### TypeScript ์ง์
์๋ฒฝํ TypeScript ์ง์์ ์ํด ํ์
์ ์๋ฅผ ํฌํจํ์ธ์:
```tsx
import {
useTwoDimensionScroll,
TwoDimensionScrollOptions,
ScrollInfo
} from 'two-dimension-scroll/react';
interface AppProps {}
const App: React.FC<AppProps> = () => {
const options: TwoDimensionScrollOptions = {
duration: 1200,
desktop: {
lerp: 0.08,
sensitivity: 1.2
},
accessibility: {
reducedMotion: true,
keyboardNavigation: true
}
};
const {
isReady,
scrollPosition,
scrollInfo,
scrollTo,
disable,
enable
} = useTwoDimensionScroll(options);
return (
// JSX ๋ด์ฉ
);
};
```
### ๋ชจ๋ฌ๊ณผ ํจ๊ป ์ฌ์ฉํ๊ธฐ
๋ชจ๋ฌ์ด ์ด๋ฆฐ ์ํ์์ ๋ฐ๋ ์คํฌ๋กค์ ์ฐจ๋จํ๊ณ ๋ชจ๋ฌ ๋ด๋ถ๋ง ์คํฌ๋กค ๊ฐ๋ฅํ๊ฒ:
```tsx
import { useModalScroll } from 'two-dimension-scroll/react';
function ModalExample() {
const { isModalOpen, openModal, closeModal } = useModalScroll();
return (
<div>
<button onClick={openModal}>๋ชจ๋ฌ ์ด๊ธฐ</button>
{isModalOpen && (
<div
className="modal-overlay"
role="dialog"
aria-modal="true"
onClick={closeModal}
>
<div
className="modal-content"
onClick={e => e.stopPropagation()}
style={{
overflow: 'auto',
maxHeight: '80vh',
padding: '20px'
}}
>
<h2>์คํฌ๋กค ๊ฐ๋ฅํ ๋ชจ๋ฌ</h2>
{/* ๊ธด ์ฝํ
์ธ */}
{Array(50).fill(0).map((_, i) => (
<p key={i}>๋ชจ๋ฌ ๋ด๋ถ ์ฝํ
์ธ {i + 1}</p>
))}
<button onClick={closeModal}>๋ซ๊ธฐ</button>
</div>
</div>
)}
</div>
);
}
```
### Next.js์์ ์ฌ์ฉํ๊ธฐ
SSR ํ๊ฒฝ์์ ์์ ํ๊ฒ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ:
```tsx
// pages/_app.tsx
import { useEffect } from 'react';
import Script from 'next/script';
export default function MyApp({ Component, pageProps }) {
return (
<>
{/* TwoDimensionScroll ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ก๋ */}
<Script
src="/js/bundle-simple.js"
strategy="beforeInteractive"
/>
<Component {...pageProps} />
</>
);
}
// components/ScrollProvider.tsx
import { useEffect, useState } from 'react';
import { useTwoDimensionScroll } from 'two-dimension-scroll/react';
export function ScrollProvider({ children }) {
const [isClient, setIsClient] = useState(false);
const { isReady } = useTwoDimensionScroll({
debug: process.env.NODE_ENV === 'development'
});
useEffect(() => {
setIsClient(true);
}, []);
if (!isClient || !isReady) {
return <div>Loading...</div>;
}
return <>{children}</>;
}
// pages/index.tsx
import { ScrollProvider } from '../components/ScrollProvider';
export default function Home() {
return (
<ScrollProvider>
<div style={{ height: '300vh' }}>
{/* ํ์ด์ง ์ฝํ
์ธ */}
</div>
</ScrollProvider>
);
}
```
### ์คํฌ๋กค ์งํ๋ฅ ์ถ์
์ค์๊ฐ ์คํฌ๋กค ์งํ๋ฅ ์ ์ถ์ ํ๋ ๋ฐฉ๋ฒ:
```tsx
import { useScrollProgress } from 'two-dimension-scroll/react';
function ScrollProgress() {
const [progress, setProgress] = useState(0);
useScrollProgress((data) => {
setProgress(data.percentage);
}, 50); // 50ms ์ค๋กํ๋ง
return (
<div style={{
position: 'fixed',
top: 0,
left: 0,
width: `${progress}%`,
height: '4px',
background: 'linear-gradient(90deg, #ff6b6b, #4ecdc4)',
transition: 'width 0.1s ease',
zIndex: 9999
}} />
);
}
```
### React Hook API ์์ ๊ฐ์ด๋
#### `useTwoDimensionScroll(options, config)`
```tsx
// ์๋ก์ด API (๊ถ์ฅ)
const {
// ์ํ ์ ๋ณด
instance, // TwoDimensionScroll ์ธ์คํด์ค
isReady, // ์ด๊ธฐํ ์๋ฃ ์ฌ๋ถ
scrollPosition, // ํ์ฌ ์คํฌ๋กค ์์น (์ค์๊ฐ)
scrollInfo, // ์คํฌ๋กค ์์ธ ์ ๋ณด { position, maxPosition, progress, isScrolling }
// ์ ์ด ํจ์
scrollTo, // (position, duration?) => void
pauseForModal, // () => void - ๋ชจ๋ฌ์ฉ ์คํฌ๋กค ์ ์ง
resumeFromModal, // () => void - ๋ชจ๋ฌ ์คํฌ๋กค ์ฌ๊ฐ
disable, // () => void - ์คํฌ๋กค ์์ ๋นํ์ฑํ
enable, // () => void - ์คํฌ๋กค ํ์ฑํ
updateOptions, // (options) => void - ์ค์๊ฐ ์ต์
๋ณ๊ฒฝ
// React ์ ์ฉ
getReactInfo // () => ReactCompatibilityInfo
} = useTwoDimensionScroll(options, { ScrollClass, deps });
// config ๋งค๊ฐ๋ณ์:
// {
// ScrollClass?: TwoDimensionScrollClass, // ํด๋์ค ์ง์ ์ ๋ฌ (๊ถ์ฅ)
// deps?: DependencyList // React ์์กด์ฑ ๋ฐฐ์ด
// }
// ๊ธฐ์กด API (ํ์ ํธํ์ฑ)
const { ... } = useTwoDimensionScroll(options, deps);
```
#### `useModalScroll()`
```tsx
const {
isModalOpen, // boolean - ๋ชจ๋ฌ ์ํ
openModal, // () => void - ๋ชจ๋ฌ ์ด๊ธฐ + ๋ฐ๋ ์คํฌ๋กค ์ฐจ๋จ
closeModal, // () => void - ๋ชจ๋ฌ ๋ซ๊ธฐ + ๋ฐ๋ ์คํฌ๋กค ๋ณต์
toggleModal // () => void - ๋ชจ๋ฌ ํ ๊ธ
} = useModalScroll();
```
#### `useScrollToTop()`
```tsx
const scrollToTop = useScrollToTop();
// ์ฌ์ฉ๋ฒ
<button onClick={() => scrollToTop(1500)}>
๋งจ ์๋ก (1.5์ด)
</button>
```
#### `useScrollProgress(callback, throttle)`
```tsx
useScrollProgress((data) => {
console.log('์คํฌ๋กค ์งํ๋ฅ :', data.percentage + '%');
console.log('ํ์ฌ ์์น:', data.position);
console.log('์งํ๋ฅ (0-1):', data.progress);
}, 100); // 100ms ๊ฐ๊ฒฉ์ผ๋ก ํธ์ถ
```
## ๐ API ๋ฌธ์
### ์์ฑ์ ์ต์
```typescript
interface TwoDimensionScrollOptions {
// ๊ธฐ๋ณธ ์ต์
duration?: number; // ์ค๋ฌด์ค ์คํฌ๋กค ์ง์ ์๊ฐ (๊ธฐ๋ณธ๊ฐ: 1000ms)
easing?: (t: number) => number; // ์ด์ง ํจ์ (๊ธฐ๋ณธ๊ฐ: easeOutCubic)
horizontalSensitivity?: number; // ๊ฐ๋ก ์คํฌ๋กค ๊ฐ๋ (๊ธฐ๋ณธ๊ฐ: 1)
verticalSensitivity?: number; // ์ธ๋ก ์คํฌ๋กค ๊ฐ๋ (๊ธฐ๋ณธ๊ฐ: 1)
disabled?: boolean; // ์คํฌ๋กค ๋นํ์ฑํ ์ฌ๋ถ (๊ธฐ๋ณธ๊ฐ: false)
debug?: boolean; // ๋๋ฒ๊ทธ ๋ชจ๋ (๊ธฐ๋ณธ๊ฐ: false)
// ํ๊ฒฝ๋ณ ์ต์ ํ ์ต์
desktop?: {
duration?: number; // PC์ฉ ์ ๋๋ฉ์ด์
์ง์์๊ฐ
lerp?: number; // ์ ํ ๋ณด๊ฐ ๊ฐ (0-1, ๋ฎ์์๋ก ๋ ๋ถ๋๋ฌ์)
sensitivity?: number; // ์ ์ฒด์ ์ธ ๊ฐ๋
wheelMultiplier?: number; // ํ ์ด๋ฒคํธ ๋ฐฐ์
touchMultiplier?: number; // ํฐ์น ์ด๋ฒคํธ ๋ฐฐ์
};
mobile?: {
duration?: number; // ๋ชจ๋ฐ์ผ์ฉ ์ ๋๋ฉ์ด์
์ง์์๊ฐ
lerp?: number; // ์ ํ ๋ณด๊ฐ ๊ฐ
sensitivity?: number; // ์ ์ฒด์ ์ธ ๊ฐ๋
wheelMultiplier?: number; // ํ ์ด๋ฒคํธ ๋ฐฐ์ (๋ชจ๋ฐ์ผ์์๋ ํฐ์น ํ )
touchMultiplier?: number; // ํฐ์น ์ด๋ฒคํธ ๋ฐฐ์
touchStopThreshold?: number; // ํฐ์น ์ ์ง ์๊ณ๊ฐ
flingMultiplier?: number; // ํ๋ง ์ ์ค์ฒ ๋ฐฐ์
};
tablet?: {
duration?: number; // ํ๋ธ๋ฆฟ์ฉ ์ ๋๋ฉ์ด์
์ง์์๊ฐ
lerp?: number; // ์ ํ ๋ณด๊ฐ ๊ฐ
sensitivity?: number; // ์ ์ฒด์ ์ธ ๊ฐ๋
wheelMultiplier?: number; // ํ ์ด๋ฒคํธ ๋ฐฐ์
touchMultiplier?: number; // ํฐ์น ์ด๋ฒคํธ ๋ฐฐ์
};
// ์ ๊ทผ์ฑ ์ต์
accessibility?: {
reducedMotion?: boolean; // prefers-reduced-motion ์๋ ๊ฐ์ง (๊ธฐ๋ณธ๊ฐ: true)
screenReader?: boolean; // ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ง์ (๊ธฐ๋ณธ๊ฐ: true)
keyboardNavigation?: boolean; // ํค๋ณด๋ ๋ค๋น๊ฒ์ด์
์ง์ (๊ธฐ๋ณธ๊ฐ: true)
ariaLiveRegion?: boolean; // ARIA Live Region ์์ฑ (๊ธฐ๋ณธ๊ฐ: true)
focusManagement?: boolean; // ํฌ์ปค์ค ๊ด๋ฆฌ ์๋ํ (๊ธฐ๋ณธ๊ฐ: true)
};
// UI/UX ์ต์
ui?: {
hideScrollbar?: boolean; // ์คํฌ๋กค๋ฐ ์จ๊น (๊ธฐ๋ณธ๊ฐ: true)
showScrollProgress?: boolean; // ์คํฌ๋กค ์งํ๋ฅ ํ์ (๊ธฐ๋ณธ๊ฐ: false)
customScrollbarStyle?: string; // ์ปค์คํ
์คํฌ๋กค๋ฐ CSS
};
}
```
### ๋ฉ์๋
#### ๊ธฐ๋ณธ ์ ์ด ๋ฉ์๋
##### `scrollTo(position: number, options?: object): void`
ํน์ ์์น๋ก ๋ถ๋๋ฝ๊ฒ ์คํฌ๋กคํฉ๋๋ค.
```javascript
// ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
scroll.scrollTo(1000); // 1000px ์์น๋ก ์คํฌ๋กค
// ์ต์
๊ณผ ํจ๊ป ์ฌ์ฉ
scroll.scrollTo(1000, {
duration: 2000, // 2์ด ๋์ ์คํฌ๋กค
immediate: false // ์ฆ์ ์คํฌ๋กค ์ฌ๋ถ
});
// React์์ ์ฌ์ฉ
const { scrollTo } = useTwoDimensionScroll();
scrollTo(0, { duration: 1500 }); // 1.5์ด ๋์ ๋งจ ์๋ก
```
##### `on(callback: ScrollCallback): void`
์คํฌ๋กค ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
```javascript
// Vanilla JS
scroll.on((data) => {
console.log('์คํฌ๋กค ์์น:', data.scroll || data.scrollTop);
console.log('์คํฌ๋กค ๋ฐฉํฅ:', data.direction);
console.log('์ด๋ฒคํธ ํ์
:', data.type);
console.log('์งํ๋ฅ :', data.progress);
});
// React Hook์ ์๋์ผ๋ก ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ฏ๋ก ์ง์ on/off ํธ์ถ ๋ถํ์
const { scrollPosition, scrollInfo } = useTwoDimensionScroll();
```
##### `updateOptions(options: Partial<TwoDimensionScrollOptions>): void`
์ค์๊ฐ์ผ๋ก ์ต์
์ ์
๋ฐ์ดํธํฉ๋๋ค.
```javascript
// Vanilla JS
scroll.updateOptions({
duration: 1500,
desktop: {
lerp: 0.05,
sensitivity: 1.5
},
accessibility: {
reducedMotion: true
}
});
// React Hook
const { updateOptions } = useTwoDimensionScroll();
updateOptions({
mobile: { duration: 600 }
});
```
#### ๋ชจ๋ฌ ๋ฐ ์ํ ์ ์ด ๋ฉ์๋
##### `pauseForModal(): void` / `resumeFromModal(): void`
๋ชจ๋ฌ ์ํ์์ ์คํฌ๋กค์ ์ ์ดํฉ๋๋ค.
```javascript
// Vanilla JS
function openModal() {
scroll.pauseForModal(); // ๋ฐ๋ ์คํฌ๋กค ์ฐจ๋จ, ์คํฌ๋กค ์์น ์ ์ฅ
showModal();
}
function closeModal() {
hideModal();
scroll.resumeFromModal(); // ๋ฐ๋ ์คํฌ๋กค ๋ณต์, ์์น ๋ณต์
}
// React Hook (๊ถ์ฅ)
const { openModal, closeModal } = useModalScroll();
// ์๋์ผ๋ก pauseForModal/resumeFromModal ์ฒ๋ฆฌ
```
##### `disable(): void` / `enable(): void`
์คํฌ๋กค์ ์์ ํ ๋นํ์ฑํ/ํ์ฑํํฉ๋๋ค.
```javascript
// Vanilla JS
scroll.disable(); // ๋ชจ๋ ์คํฌ๋กค ์ด๋ฒคํธ ๋นํ์ฑํ
scroll.enable(); // ์คํฌ๋กค ์ฌํ์ฑํ
// React Hook
const { disable, enable } = useTwoDimensionScroll();
```
#### ์ ๋ณด ์กฐํ ๋ฉ์๋
##### `getCurrentPosition(): number` / `getMaxPosition(): number`
ํ์ฌ ๋ฐ ์ต๋ ์คํฌ๋กค ์์น๋ฅผ ๋ฐํํฉ๋๋ค.
```javascript
// Vanilla JS
const current = scroll.getCurrentPosition();
const max = scroll.getMaxPosition();
const progress = current / max;
// React Hook (์ค์๊ฐ ์๋ ์
๋ฐ์ดํธ)
const { scrollPosition, scrollInfo } = useTwoDimensionScroll();
console.log('ํ์ฌ ์์น:', scrollPosition);
console.log('์ต๋ ์์น:', scrollInfo.maxPosition);
console.log('์งํ๋ฅ :', scrollInfo.progress);
```
##### `getReactCompatibilityInfo(): ReactCompatibilityInfo` (React ์ ์ฉ)
React ํ๊ฒฝ ์ ๋ณด๋ฅผ ์กฐํํฉ๋๋ค.
```javascript
const { getReactInfo } = useTwoDimensionScroll();
const info = getReactInfo();
console.log('React ํ๊ฒฝ:', info.isReactEnvironment);
console.log('Router ๊ฐ์ง:', info.hasReactRouter);
console.log('์ด๋ฒคํธ ์:', info.eventListenerCount);
```
#### ํ๊ฒฝ ๋ฐ ์ฑ๋ฅ ์ต์ ํ ๋ฉ์๋
##### `updateEnvironmentOptions(environment: string, options: object): void`
ํน์ ํ๊ฒฝ์ ์ต์
๋ง ์
๋ฐ์ดํธํฉ๋๋ค.
```javascript
// ๋ชจ๋ฐ์ผ ํ๊ฒฝ๋ง ๋ณ๋ ์ต์ ํ
scroll.updateEnvironmentOptions('mobile', {
duration: 500,
lerp: 0.2,
touchStopThreshold: 5
});
// ๋ฐ์คํฌํฑ ์ฑ๋ฅ ์ต์ ํ
scroll.updateEnvironmentOptions('desktop', {
lerp: 0.08,
wheelMultiplier: 1.5
});
```
##### `applyPerformancePreset(preset: string): void`
๋ฏธ๋ฆฌ ์ ์๋ ์ฑ๋ฅ ํ๋ฆฌ์
์ ์ ์ฉํฉ๋๋ค.
```javascript
scroll.applyPerformancePreset('smooth'); // ๋ถ๋๋ฌ์ด ์คํฌ๋กค ์ฐ์
scroll.applyPerformancePreset('performance'); // ์ฑ๋ฅ ์ฐ์
scroll.applyPerformancePreset('accessibility'); // ์ ๊ทผ์ฑ ์ฐ์
```
#### ์ ๋ฆฌ ๋ฐ ํด์ ๋ฉ์๋
##### `destroy(): void`
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์์ ํ ํด์ ํฉ๋๋ค.
```javascript
// Vanilla JS
scroll.destroy();
// React Hook์ useEffect cleanup์์ ์๋ ํธ์ถ
// ์๋ ํธ์ถ ์:
const { instance } = useTwoDimensionScroll();
useEffect(() => {
return () => {
if (instance) {
instance.destroy();
}
};
}, []);
```
##### `cleanup(): () => void` (React ์ ์ฉ)
React useEffect cleanup ํจ์๋ฅผ ๋ฐํํฉ๋๋ค.
```javascript
const { instance } = useTwoDimensionScroll();
useEffect(() => {
// ๋ค๋ฅธ ์ด๊ธฐํ ๋ก์ง...
return instance?.cleanup?.(); // ์์ ํ cleanup
}, [instance]);
```
## ๐จ ์ด์ง ํจ์
๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ด์ฅ๋ ์ด์ง ํจ์๋ค:
```javascript
import { Easing } from 'two-dimension-scroll';
const scroll = new TwoDimensionScroll({
easing: Easing.easeInOutCubic // ์ฌ์ฉ ๊ฐ๋ฅํ ์ต์
๋ค:
// Easing.linear
// Easing.easeInQuad
// Easing.easeOutQuad
// Easing.easeInOutQuad
// Easing.easeInCubic
// Easing.easeOutCubic
// Easing.easeInOutCubic
});
// ์ปค์คํ
์ด์ง ํจ์
const scroll = new TwoDimensionScroll({
easing: (t) => t * t * t // ์ปค์คํ
cubic easing
});
```
## ๐ก ์ฌ์ฉ ์ฌ๋ก ๋ฐ ์ค์ ์์
### 1. ๊ธฐ๋ณธ ๋ถ๋๋ฌ์ด ์คํฌ๋กค
```javascript
// Vanilla JS
const scroll = new TwoDimensionScroll({
duration: 800,
easing: Easing.easeOutQuad,
desktop: {
lerp: 0.1
},
mobile: {
lerp: 0.15
}
});
```
```tsx
// React Hook
function SmoothScrollApp() {
const { isReady } = useTwoDimensionScroll({
duration: 800,
desktop: { lerp: 0.1 },
mobile: { lerp: 0.15 }
});
return (
<div style={{ height: '300vh' }}>
{isReady ? '๋ถ๋๋ฌ์ด ์คํฌ๋กค ์ค๋น ์๋ฃ!' : 'Loading...'}
</div>
);
}
```
### 2. ํ๊ฒฝ๋ณ ๊ฐ๋ ์ต์ ํ
```javascript
// Vanilla JS - ์ธ๋ฐํ ํ๊ฒฝ๋ณ ์ค์
const scroll = new TwoDimensionScroll({
desktop: {
sensitivity: 1.2, // PC์์ ๋ ๋ฏผ๊ฐํ๊ฒ
wheelMultiplier: 1.5, // ํ ์คํฌ๋กค ๋ฐฐ์
duration: 1000
},
mobile: {
sensitivity: 0.8, // ๋ชจ๋ฐ์ผ์์ ๋ ๋ฏผ๊ฐํ๊ฒ
touchMultiplier: 1.0, // ํฐ์น ์คํฌ๋กค ๋ฐฐ์
duration: 600,
touchStopThreshold: 5 // ํฐ์น ์ ์ง ๊ฐ๋
},
tablet: {
sensitivity: 1.0, // ํ๋ธ๋ฆฟ ์ค๊ฐ๊ฐ
duration: 800
}
});
```
```tsx
// React Hook - ๋ฐ์ํ ์ค์
function ResponsiveScrollApp() {
const { scrollPosition, updateOptions } = useTwoDimensionScroll({
desktop: {
sensitivity: 1.2,
duration: 1000
},
mobile: {
sensitivity: 0.8,
duration: 600
}
});
// ํ๋ฉด ํฌ๊ธฐ ๋ณ๊ฒฝ ์ ์ค์ ๋์ ๋ณ๊ฒฝ
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 768) {
updateOptions({
mobile: { sensitivity: 0.6 } // ์์ ํ๋ฉด์์ ๋ ๋ถ๋๋ฝ๊ฒ
});
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [updateOptions]);
return <div>ํ์ฌ ์คํฌ๋กค: {scrollPosition}px</div>;
}
```
### 3. ๊ณ ๊ธ ๋ชจ๋ฌ ์คํฌ๋กค ์ ์ด
```javascript
// Vanilla JS - ์ ๊ตํ ๋ชจ๋ฌ ์ ์ด
class ModalManager {
constructor() {
this.scroll = new TwoDimensionScroll({
debug: true
});
this.modals = [];
}
openModal(modalId) {
this.scroll.pauseForModal();
this.modals.push(modalId);
console.log('๋ชจ๋ฌ ์ด๋ฆผ, ๋ฐ๋ ์คํฌ๋กค ์ฐจ๋จ');
}
closeModal(modalId) {
this.modals = this.modals.filter(id => id !== modalId);
if (this.modals.length === 0) {
this.scroll.resumeFromModal();
console.log('๋ชจ๋ ๋ชจ๋ฌ ๋ซํ, ๋ฐ๋ ์คํฌ๋กค ๋ณต์');
}
}
}
```
```tsx
// React Hook - ์ค์ฒฉ ๋ชจ๋ฌ ์ง์
function AdvancedModalApp() {
const [modals, setModals] = useState([]);
const { pauseForModal, resumeFromModal } = useTwoDimensionScroll();
const openModal = useCallback((modalId) => {
setModals(prev => {
if (prev.length === 0) {
pauseForModal(); // ์ฒซ ๋ฒ์งธ ๋ชจ๋ฌ์ผ ๋๋ง ์คํฌ๋กค ์ฐจ๋จ
}
return [...prev, modalId];
});
}, [pauseForModal]);
const closeModal = useCallback((modalId) => {
setModals(prev => {
const newModals = prev.filter(id => id !== modalId);
if (newModals.length === 0) {
resumeFromModal(); // ๋ง์ง๋ง ๋ชจ๋ฌ์ผ ๋๋ง ์คํฌ๋กค ๋ณต์
}
return newModals;
});
}, [resumeFromModal]);
return (
<div>
<button onClick={() => openModal('modal1')}>๋ชจ๋ฌ 1 ์ด๊ธฐ</button>
<button onClick={() => openModal('modal2')}>๋ชจ๋ฌ 2 ์ด๊ธฐ</button>
{modals.includes('modal1') && (
<Modal onClose={() => closeModal('modal1')}>
<button onClick={() => openModal('modal2')}>์ค์ฒฉ ๋ชจ๋ฌ ์ด๊ธฐ</button>
</Modal>
)}
{modals.includes('modal2') && (
<Modal onClose={() => closeModal('modal2')}>
์ค์ฒฉ๋ ๋ชจ๋ฌ ๋ด์ฉ
</Modal>
)}
</div>
);
}
```
### 4. ์ค์๊ฐ ์คํฌ๋กค ์งํ๋ฅ ๋ฐ ๋ค๋น๊ฒ์ด์
```javascript
// Vanilla JS - ์น์
๊ธฐ๋ฐ ๋ค๋น๊ฒ์ด์
const scroll = new TwoDimensionScroll({
debug: false
});
const sections = document.querySelectorAll('.section');
let currentSection = 0;
scroll.on((data) => {
const progress = data.scroll / scroll.limit;
const percentage = Math.round(progress * 100);
// ์งํ๋ฅ ๋ฐ ์
๋ฐ์ดํธ
document.getElementById('progress-bar').style.width = `${percentage}%`;
// ํ์ฌ ์น์
๊ฐ์ง
sections.forEach((section, index) => {
const rect = section.getBoundingClientRect();
if (rect.top <= window.innerHeight / 2 && rect.bottom >= window.innerHeight / 2) {
if (currentSection !== index) {
currentSection = index;
updateNavigation(index);
}
}
});
});
function updateNavigation(activeIndex) {
document.querySelectorAll('.nav-item').forEach((item, index) => {
item.classList.toggle('active', index === activeIndex);
});
}
```
```tsx
// React Hook - ๊ณ ๊ธ ์งํ๋ฅ ์ปดํฌ๋ํธ
function ProgressiveScrollApp() {
const { scrollPosition, scrollInfo } = useTwoDimensionScroll();
const [currentSection, setCurrentSection] = useState(0);
const [progress, setProgress] = useState(0);
const sectionsRef = useRef([]);
useScrollProgress((data) => {
setProgress(data.percentage);
}, 16); // 60fps๋ก ์
๋ฐ์ดํธ
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const index = sectionsRef.current.findIndex(
ref => ref.current === entry.target
);
if (index !== -1) {
setCurrentSection(index);
}
}
});
},
{ threshold: 0.5 }
);
sectionsRef.current.forEach(ref => {
if (ref.current) observer.observe(ref.current);
});
return () => observer.disconnect();
}, []);
return (
<div>
{/* ์งํ๋ฅ ๋ฐ */}
<div style={{
position: 'fixed',
top: 0,
left: 0,
width: `${progress}%`,
height: '4px',
backgroundColor: '#007bff',
zIndex: 1000,
transition: 'width 0.1s ease'
}} />
{/* ์น์
๋ค๋น๊ฒ์ด์
*/}
<nav style={{ position: 'fixed', right: 20, top: '50%' }}>
{[1, 2, 3, 4].map((num, index) => (
<div
key={num}
style={{
width: 12,
height: 12,
borderRadius: '50%',
backgroundColor: currentSection === index ? '#007bff' : '#ccc',
margin: '8px 0',
cursor: 'pointer'
}}
onClick={() => {
const section = sectionsRef.current[index]?.current;
if (section) {
const top = section.offsetTop;
scrollTo(top, { duration: 1000 });
}
}}
/>
))}
</nav>
{/* ์น์
๋ค */}
{[1, 2, 3, 4].map((num, index) => (
<section
key={num}
ref={el => sectionsRef.current[index] = { current: el }}
style={{ height: '100vh', padding: '50px' }}
className="section"
>
<h2>์น์
{num}</h2>
<p>ํ์ฌ ์คํฌ๋กค ์์น: {Math.round(scrollPosition)}px</p>
<p>์คํฌ๋กค ์งํ๋ฅ : {progress}%</p>
</section>
))}
</div>
);
}
```
### 5. ์ ๊ทผ์ฑ ์ต์ ํ ์คํฌ๋กค
```javascript
// ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ ์ค์
const accessibleScroll = new TwoDimensionScroll({
accessibility: {
reducedMotion: true, // prefers-reduced-motion ์๋ ๊ฐ์ง
screenReader: true, // ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ง์
keyboardNavigation: true, // ํค๋ณด๋ ๋ค๋น๊ฒ์ด์
ariaLiveRegion: true, // ARIA ๋ผ์ด๋ธ ๋ฆฌ์
focusManagement: true // ํฌ์ปค์ค ๊ด๋ฆฌ
},
desktop: {
duration: 800 // ์ ๋นํ ์๋
},
mobile: {
duration: 600 // ๋ชจ๋ฐ์ผ์์ ๋น ๋ฅด๊ฒ
}
});
```
```tsx
// React - ์ ๊ทผ์ฑ ์นํ์ ์คํฌ๋กค
function AccessibleScrollApp() {
const { scrollTo, scrollPosition } = useTwoDimensionScroll({
accessibility: {
reducedMotion: true,
keyboardNavigation: true,
screenReader: true
}
});
const handleKeyDown = useCallback((e) => {
switch (e.key) {
case 'Home':
e.preventDefault();
scrollTo(0, { duration: 800 });
break;
case 'End':
e.preventDefault();
scrollTo(document.body.scrollHeight, { duration: 800 });
break;
case 'PageUp':
e.preventDefault();
scrollTo(scrollPosition - window.innerHeight, { duration: 600 });
break;
case 'PageDown':
e.preventDefault();
scrollTo(scrollPosition + window.innerHeight, { duration: 600 });
break;
}
}, [scrollTo, scrollPosition]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [handleKeyDown]);
return (
<div role="main" aria-label="๋ถ๋๋ฌ์ด ์คํฌ๋กค ํ์ด์ง">
<div style={{ height: '300vh', padding: '20px' }}>
<h1>์ ๊ทผ์ฑ ์นํ ์คํฌ๋กค</h1>
<p>ํค๋ณด๋๋ก ์คํฌ๋กคํ์ธ์: Home, End, PageUp, PageDown</p>
<button
onClick={() => scrollTo(0)}
aria-label="ํ์ด์ง ์๋จ์ผ๋ก ์ด๋"
>
๋งจ ์๋ก
</button>
</div>
</div>
);
}
```
## ๐ ๋ธ๋ผ์ฐ์ ์ง์
- โ
Chrome 60+
- โ
Firefox 55+
- โ
Safari 12+
- โ
Edge 79+
- โ
iOS Safari 12+
- โ
Android Chrome 60+
## ๐ฑ ํ๊ฒฝ๋ณ ์ต์ ํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ฉ์์ ํ๊ฒฝ(PC/๋ชจ๋ฐ์ผ/ํ๋ธ๋ฆฟ)์ ์๋์ผ๋ก ๊ฐ์งํ์ฌ ์ต์ ์ ์คํฌ๋กค ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
### ์๋ ํ๊ฒฝ ๊ฐ์ง
```javascript
// ํ๊ฒฝ๋ณ ์๋ ์ต์ ํ
const scroll = new TwoDimensionScroll({
desktop: {
duration: 1000,
lerp: 0.1,
sensitivity: 1.2
},
mobile: {
duration: 600, // ๋ชจ๋ฐ์ผ์์ ๋ ๋น ๋ฅธ ๋ฐ์
lerp: 0.15, // ๋ ์ง์ ์ ์ธ ๋ฐ์
touchStopThreshold: 3 // ํฐ์น ์ ์ง ๊ฐ๋
},
tablet: {
duration: 800, // ์ค๊ฐ๊ฐ
lerp: 0.12
}
});
// ํ์ฌ ํ๊ฒฝ ์ ๋ณด ํ์ธ
console.log(scroll.getEnvironmentInfo());
```
### React์์ ํ๊ฒฝ๋ณ ๋์
```tsx
function ResponsiveScrollComponent() {
const { instance, scrollPosition } = useTwoDimensionScroll({
desktop: { duration: 1000 },
mobile: { duration: 600 },
tablet: { duration: 800 }
});
useEffect(() => {
if (instance) {
const envInfo = instance.getEnvironmentInfo();
console.log('ํ์ฌ ํ๊ฒฝ:', envInfo.currentEnvironment);
console.log('ํฐ์น ์ง์:', envInfo.isTouchDevice);
}
}, [instance]);
return <div>ํ๊ฒฝ ์ต์ ํ ์คํฌ๋กค ํ์ฑํ</div>;
}
```
## โ๏ธ React ๊ฐ๋ฐ ํ
### ์ฑ๋ฅ ์ต์ ํ
```tsx
// 1. ๋ฉ๋ชจ์ด์ ์ด์
์ผ๋ก ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ๋ฐฉ์ง
const ScrollComponent = memo(() => {
const { scrollPosition, scrollTo } = useTwoDimensionScroll();
return (
<div>์์น: {scrollPosition}</div>
);
});
// 2. ์คํฌ๋กค ์ฝ๋ฐฑ ์ต์ ํ
function OptimizedScrollApp() {
const scrollCallbackRef = useRef();
const { instance } = useTwoDimensionScroll();
useEffect(() => {
if (!instance) return;
// ์ค๋กํ๋ง๋ ์ฝ๋ฐฑ
scrollCallbackRef.current = throttle((data) => {
console.log('์คํฌ๋กค:', data.scroll);
}, 100);
instance.on(scrollCallbackRef.current);
return () => {
if (scrollCallbackRef.current) {
instance.off(scrollCallbackRef.current);
}
};
}, [instance]);
}
```
### ๋๋ฒ๊น
๋ฐ ๊ฐ๋ฐ ๋ชจ๋
```tsx
// ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ๋๋ฒ๊น
ํ์ฑํ
const { instance, getReactInfo } = useTwoDimensionScroll({
debug: process.env.NODE_ENV === 'development'
});
// React ํธํ์ฑ ์ ๋ณด ํ์ธ
useEffect(() => {
if (process.env.NODE_ENV === 'development') {
console.log('React ํธํ์ฑ:', getReactInfo());
}
}, [getReactInfo]);
```
### ์ผ๋ฐ์ ์ธ ๋ฌธ์ ํด๊ฒฐ
#### 1. SSR ์ค๋ฅ ํด๊ฒฐ
```tsx
// Next.js์์ hydration ์ค๋ฅ ๋ฐฉ์ง
function SSRSafeScrollApp() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
const { isReady } = useTwoDimensionScroll();
if (!isClient || !isReady) {
return <div>Loading...</div>;
}
return <div>์คํฌ๋กค ์ค๋น ์๋ฃ</div>;
}
```
#### 2. ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง
```tsx
// ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์ ํ์คํ ์ ๋ฆฌ
function SafeScrollComponent() {
const scrollRef = useRef(null);
useEffect(() => {
return () => {
// ๊ฐ์ ์ ๋ฆฌ
if (scrollRef.current) {
scrollRef.current.destroy();
scrollRef.current = null;
}
};
}, []);
const { instance } = useTwoDimensionScroll();
scrollRef.current = instance;
return <div>์์ ํ ์คํฌ๋กค ์ปดํฌ๋ํธ</div>;
}
```
#### 3. ์ํ ๋๊ธฐํ
```tsx
// ์ธ๋ถ ์ํ์ ์คํฌ๋กค ์์น ๋๊ธฐํ
function SyncedScrollApp() {
const [externalScroll, setExternalScroll] = useState(0);
const { scrollPosition, scrollTo } = useTwoDimensionScroll();
// ์ธ๋ถ ์ํ ๋ณ๊ฒฝ ์ ์คํฌ๋กค ์์น ์
๋ฐ์ดํธ
useEffect(() => {
if (Math.abs(externalScroll - scrollPosition) > 10) {
scrollTo(externalScroll);
}
}, [externalScroll, scrollPosition, scrollTo]);
// ์คํฌ๋กค ์์น ๋ณ๊ฒฝ ์ ์ธ๋ถ ์ํ ์
๋ฐ์ดํธ
useEffect(() => {
setExternalScroll(scrollPosition);
}, [scrollPosition]);
}
```
### TypeScript ํ์
ํ์ฉ
```tsx
import type {
TwoDimensionScrollOptions,
ScrollInfo,
TwoDimensionScrollHookReturn
} from 'two-dimension-scroll/react';
interface ScrollPageProps {
initialOptions?: TwoDimensionScrollOptions;
onScrollChange?: (info: ScrollInfo) => void;
}
const ScrollPage: React.FC<ScrollPageProps> = ({
initialOptions,
onScrollChange
}) => {
const scrollHook: TwoDimensionScrollHookReturn = useTwoDimensionScroll(
initialOptions
);
useEffect(() => {
if (scrollHook.scrollInfo && onScrollChange) {
onScrollChange(scrollHook.scrollInfo);
}
}, [scrollHook.scrollInfo, onScrollChange]);
return <div>ํ์
์์ ํ ์คํฌ๋กค ํ์ด์ง</div>;
};
```
## ๐ง ๊ฐ๋ฐ ํ๊ฒฝ ์ค์
### ์์กด์ฑ ์ค์น
```bash
npm install
```
### ๋น๋
```bash
npm run build
```
### ๊ฐ๋ฐ ๋ชจ๋ (watch)
```bash
npm run dev
```
### ๋ฐ๋ชจ ์คํ
```bash
npm run serve
```
๋ธ๋ผ์ฐ์ ์์ `http://localhost:3000`์ผ๋ก ์ ์ํ์ฌ ๋ฐ๋ชจ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
## ๐ฎ ๋ฐ๋ชจ ํ์ด์ง
ํ๋ก์ ํธ์ ํฌํจ๋ `index.html` ํ์ผ์ ํตํด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ฒดํํด๋ณผ ์ ์์ต๋๋ค:
- ๋ค์ํ ์คํฌ๋กค ๋ฐฉ์ ํ
์คํธ
- ์ค์๊ฐ ์ค์ ๋ณ๊ฒฝ
- ๋๋ฒ๊ทธ ๋ชจ๋๋ก ์ด๋ฒคํธ ํ์ธ
- ๋ชจ๋ฐ์ผ๊ณผ ๋ฐ์คํฌํฑ ํ๊ฒฝ ๋ชจ๋ ์ง์
## ๐ค ๊ธฐ์ฌํ๊ธฐ
1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## ๐ ๋ผ์ด์ ์ค
MIT License. ์์ธํ ๋ด์ฉ์ [LICENSE](LICENSE) ํ์ผ์ ์ฐธ์กฐํ์ธ์.
## ๐ ๊ฐ์ฌ์ ๋ง
์ด ํ๋ก์ ํธ๋ [Lenis](https://github.com/studio-freight/lenis)์ ๊ฐ์ ํ๋ฅญํ ์คํฌ๋กค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์์ ์๊ฐ์ ๋ฐ์์ต๋๋ค.
---
**Made with โค๏ธ for developers who love smooth scrolling**
## ๐ Build Scripts
### Development Build (์ฝ์ ๋ก๊ทธ ์ ์ง)
```bash
npm run build:dev
```
- ์ฝ์ ๋ก๊ทธ ์ ์ง (๋๋ฒ๊น
์ฉ)
- ์์ค๋งต ํฌํจ
- ์์ถ ์์
### Production Build (์ฝ์ ๋ก๊ทธ ์ ๊ฑฐ)
```bash
npm run build:prod
# ๋๋
npm run build
```
- **๋ชจ๋ ์ฝ์ ๋ก๊ทธ ์๋ ์ ๊ฑฐ** (`console.log`, `console.info`, `console.debug` ๋ฑ)
- `console.error`, `console.warn`์ ์ ์ง (์ต์
)
- ์ฝ๋ ์์ถ ๋ฐ ์ต์ ํ
- ์์ค๋งต ์ ๊ฑฐ
### Watch Mode (๊ฐ๋ฐ์ฉ)
```bash
npm run dev
```
- ํ์ผ ๋ณ๊ฒฝ ์ ์๋ ์ฌ๋น๋
- ๊ฐ๋ฐ ๋ชจ๋๋ก ๋น๋
---
## ๐ฆ Build Configuration
### ์ฝ์ ๋ก๊ทธ ์ ๊ฑฐ ์ค์
- **ํ๋ก๋์
๋น๋**: ๋ชจ๋ ๋๋ฒ๊ทธ ๋ก๊ทธ ์๋ ์ ๊ฑฐ
- **๊ฐ๋ฐ ๋น๋**: ๋๋ฒ๊ทธ ๋ก๊ทธ ์ ์ง (๋๋ฒ๊น
ํธ์์ฑ)
- **Error/Warn**: ํ๋ก๋์
์์๋ ์ ์ง (์ ํ ๊ฐ๋ฅ)
### ๋น๋ ๋๊ตฌ
- **Rollup**: ๋ชจ๋ ๋ฒ๋ค๋ง
- **Babel**: ์ฝ๋ ๋ณํ ๋ฐ ์ฝ์ ๋ก๊ทธ ์ ๊ฑฐ
- **TypeScript**: ํ์
์ ์ ์์ฑ
- **Terser**: ์ฝ๋ ์์ถ (ํ๋ก๋์
)
---