npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

two-dimension-scroll

v1.4.1

Published

A smooth scroll library that detects both horizontal and vertical scroll and converts them to vertical smooth scrolling

Downloads

27

Readme

TwoDimensionScroll 📱🖱️

가로와 세로 스크롤을 모두 감지하여 부드러운 세로 스크롤로 변환하는 혁신적인 JavaScript 라이브러리입니다.

✨ 주요 기능

  • 🎯 2차원 스크롤 감지: 트랙패드나 매직마우스의 가로 스크롤을 자동으로 세로 스크롤로 변환
  • 🌊 부드러운 애니메이션: requestAnimationFrame을 사용한 60fps 부드러운 스크롤
  • 📱 모바일 최적화: 환경별 최적화된 스크롤 경험 (PC/모바일/태블릿)
  • ⚛️ React 완벽 지원: 공식 React Hook과 TypeScript 타입 정의 제공
  • 🎭 모달 친화적: 모달 상태에서 완벽한 스크롤 제어 (바디 차단 + 내부 허용)
  • ♿ 접근성 지원: prefers-reduced-motion, 스크린 리더, 키보드 네비게이션 완벽 지원
  • 🎛️ 다양한 옵션: 감도, 지속시간, 이징 함수 등 세밀한 커스터마이징
  • ⚡ 고성능: 메모리 효율적이고 배터리 친화적인 구조
  • 🔧 TypeScript 지원: 완전한 타입 정의 포함

🚀 시작하기

설치

npm install two-dimension-scroll

Vanilla 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 미지원

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 호환)

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 사용

<script src="path/to/dist/bundle-simple.js"></script>
<script>
  const scroll = new TwoDimensionScroll({
    duration: 1000,
    debug: true
  });
</script>

⚛️ React에서 사용하기

TwoDimensionScroll은 React 환경을 완벽하게 지원하여 최고의 개발 경험을 제공합니다.

React 설치 및 설정

# npm으로 설치
npm install two-dimension-scroll

# 또는 yarn으로 설치
yarn add two-dimension-scroll
// React Hook import
import { 
  useTwoDimensionScroll,
  useModalScroll,
  useScrollToTop,
  useScrollProgress
} from 'two-dimension-scroll/react';

기본 React Hook 사용법

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 지원을 위해 타입 정의를 포함하세요:

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 내용
  );
};

모달과 함께 사용하기

모달이 열린 상태에서 바디 스크롤을 차단하고 모달 내부만 스크롤 가능하게:

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 환경에서 안전하게 사용하는 방법:

// 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>
  );
}

스크롤 진행률 추적

실시간 스크롤 진행률을 추적하는 방법:

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)

// 새로운 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()

const {
  isModalOpen,       // boolean - 모달 상태
  openModal,         // () => void - 모달 열기 + 바디 스크롤 차단
  closeModal,        // () => void - 모달 닫기 + 바디 스크롤 복원
  toggleModal        // () => void - 모달 토글
} = useModalScroll();

useScrollToTop()

const scrollToTop = useScrollToTop();

// 사용법
<button onClick={() => scrollToTop(1500)}>
  맨 위로 (1.5초)
</button>

useScrollProgress(callback, throttle)

useScrollProgress((data) => {
  console.log('스크롤 진행률:', data.percentage + '%');
  console.log('현재 위치:', data.position);
  console.log('진행률 (0-1):', data.progress);
}, 100); // 100ms 간격으로 호출

📖 API 문서

생성자 옵션

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

특정 위치로 부드럽게 스크롤합니다.

// 기본 사용법
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

스크롤 이벤트 리스너를 추가합니다.

// 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

실시간으로 옵션을 업데이트합니다.

// 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

모달 상태에서 스크롤을 제어합니다.

// Vanilla JS
function openModal() {
  scroll.pauseForModal();    // 바디 스크롤 차단, 스크롤 위치 저장
  showModal();
}

function closeModal() {
  hideModal();
  scroll.resumeFromModal();  // 바디 스크롤 복원, 위치 복원
}

// React Hook (권장)
const { openModal, closeModal } = useModalScroll();
// 자동으로 pauseForModal/resumeFromModal 처리
disable(): void / enable(): void

스크롤을 완전히 비활성화/활성화합니다.

// Vanilla JS
scroll.disable();  // 모든 스크롤 이벤트 비활성화
scroll.enable();   // 스크롤 재활성화

// React Hook
const { disable, enable } = useTwoDimensionScroll();

정보 조회 메서드

getCurrentPosition(): number / getMaxPosition(): number

현재 및 최대 스크롤 위치를 반환합니다.

// 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 환경 정보를 조회합니다.

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

특정 환경의 옵션만 업데이트합니다.

// 모바일 환경만 별도 최적화
scroll.updateEnvironmentOptions('mobile', {
  duration: 500,
  lerp: 0.2,
  touchStopThreshold: 5
});

// 데스크톱 성능 최적화
scroll.updateEnvironmentOptions('desktop', {
  lerp: 0.08,
  wheelMultiplier: 1.5
});
applyPerformancePreset(preset: string): void

미리 정의된 성능 프리셋을 적용합니다.

scroll.applyPerformancePreset('smooth');      // 부드러운 스크롤 우선
scroll.applyPerformancePreset('performance'); // 성능 우선
scroll.applyPerformancePreset('accessibility'); // 접근성 우선

정리 및 해제 메서드

destroy(): void

라이브러리를 완전히 해제합니다.

// Vanilla JS
scroll.destroy();

// React Hook은 useEffect cleanup에서 자동 호출
// 수동 호출 시:
const { instance } = useTwoDimensionScroll();
useEffect(() => {
  return () => {
    if (instance) {
      instance.destroy();
    }
  };
}, []);
cleanup(): () => void (React 전용)

React useEffect cleanup 함수를 반환합니다.

const { instance } = useTwoDimensionScroll();

useEffect(() => {
  // 다른 초기화 로직...
  
  return instance?.cleanup?.(); // 안전한 cleanup
}, [instance]);

🎨 이징 함수

라이브러리에 내장된 이징 함수들:

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. 기본 부드러운 스크롤

// Vanilla JS
const scroll = new TwoDimensionScroll({
  duration: 800,
  easing: Easing.easeOutQuad,
  desktop: {
    lerp: 0.1
  },
  mobile: {
    lerp: 0.15
  }
});
// 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. 환경별 감도 최적화

// 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
  }
});
// 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. 고급 모달 스크롤 제어

// 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('모든 모달 닫힘, 바디 스크롤 복원');
    }
  }
}
// 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. 실시간 스크롤 진행률 및 네비게이션

// 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);
  });
}
// 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. 접근성 최적화 스크롤

// 접근성을 고려한 설정
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              // 모바일에서 빠르게
  }
});
// 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/모바일/태블릿)을 자동으로 감지하여 최적의 스크롤 경험을 제공합니다.

자동 환경 감지

// 환경별 자동 최적화
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에서 환경별 대응

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 개발 팁

성능 최적화

// 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]);
}

디버깅 및 개발 모드

// 개발 환경에서만 디버깅 활성화
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 오류 해결

// 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. 메모리 누수 방지

// 컴포넌트 언마운트 시 확실한 정리
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. 상태 동기화

// 외부 상태와 스크롤 위치 동기화
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 타입 활용

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>;
};

🔧 개발 환경 설정

의존성 설치

npm install

빌드

npm run build

개발 모드 (watch)

npm run dev

데모 실행

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 파일을 참조하세요.

🙏 감사의 말

이 프로젝트는 Lenis와 같은 훌륭한 스크롤 라이브러리들에서 영감을 받았습니다.


Made with ❤️ for developers who love smooth scrolling

🚀 Build Scripts

Development Build (콘솔 로그 유지)

npm run build:dev
  • 콘솔 로그 유지 (디버깅용)
  • 소스맵 포함
  • 압축 없음

Production Build (콘솔 로그 제거)

npm run build:prod
# 또는
npm run build
  • 모든 콘솔 로그 자동 제거 (console.log, console.info, console.debug 등)
  • console.error, console.warn은 유지 (옵션)
  • 코드 압축 및 최적화
  • 소스맵 제거

Watch Mode (개발용)

npm run dev
  • 파일 변경 시 자동 재빌드
  • 개발 모드로 빌드

📦 Build Configuration

콘솔 로그 제거 설정

  • 프로덕션 빌드: 모든 디버그 로그 자동 제거
  • 개발 빌드: 디버그 로그 유지 (디버깅 편의성)
  • Error/Warn: 프로덕션에서도 유지 (선택 가능)

빌드 도구

  • Rollup: 모듈 번들링
  • Babel: 코드 변환 및 콘솔 로그 제거
  • TypeScript: 타입 정의 생성
  • Terser: 코드 압축 (프로덕션)