통합검색

Javascript

[Javascript] fullpage (GSAP)

  • 2025.08.18 08:55:51
[!]HTML[/!]
 
<section class="section">
    <h3>visual</h3>
</section>

<section class="section">
    <h3>1</h3>
</section>

<section class="section">
    <h3>2</h3>
</section>

<section class="section">
    <h3>4</h3>
</section>

<section class="section">
    <h3>5</h3>
</section>

<section class="section foot">
    <h3>footer</h3>
</section>




[!]CSS[/!]
 
#main .section {display: flex;justify-content: center;align-items: center;flex-wrap: wrap;}
#main .section h3 {font-size: 40px;color: #222;font-weight: 700;}
#main .section:nth-child(odd) {background: #000;}
#main .section:nth-child(odd) h3 {color: #fff;}
#main .section:not(.foot) {min-height: 100vh;}
#main .section.foot {height: 600px;}




[!]Javascript[/!]
 
// fullpage
const main_fullpage = {
    init: function () {
        this.action();
    },
    action: function () {
   
        gsap.registerPlugin(ScrollToPlugin);
        const wrap = document.getElementById('main') || document.body;
        const secs = Array.from(document.querySelectorAll('#main .section, .section')); // #main 기준 없으면 .section 전역
        if (!secs.length) return;
        let tops = [];
        let current = 0;
        let speed = 0.8;
        let isAnimating = false;
        // active
        const setActive = () => {
            secs.forEach((sec, i) => sec.classList.toggle('active', i === current));
            // console.log(current);
            const head = document.querySelector('#header');
            if(current != '0'){
                head.classList.add('bg');
            }else{
                head.classList.remove('bg');
            }
        };
        const calcPos = () => {
            tops = [];
            let i = 0, len = secs.length;
            while (i < len) { tops.push(secs[i].offsetTop); i++; }
            // 현재 스크롤 위치에 가장 가까운 섹션 인덱스 갱신
            const y = window.scrollY || 0;
            current = nearestIndex(y, tops);
            setActive();
        };
        const nearestIndex = (y, arr) => {
            let idx = 0, min = Math.abs((arr[0] || 0) - y);
            let i = 1, len = arr.length;
            while (i < len) {
            const d = Math.abs(arr[i] - y);
            if (d < min) { min = d; idx = i; }
            i++;
            }
            return idx;
        };
        const goTo = (y) => {
            isAnimating = true;
            gsap.to(window, {
            'scrollTo' : { 'y' : y, 'autoKill' : false },
            'duration' : speed,
            'ease' : 'power2.out',
            'onComplete': () => {
                isAnimating = false;
                setActive(); // 이동 후 active 처리
            }
            });
        };
        const next = () => {
            if (isAnimating) return;
            if (current < tops.length - 1) { current += 1; goTo(tops[current]); }
            else { // 마지막(footer 포함)에서 더 내리면 문서 최하단
            const maxY = document.documentElement.scrollHeight - window.innerHeight;
            goTo(maxY < 0 ? 0 : maxY);
            }
        };
        const prev = () => {
            if (isAnimating) return;
            if (current > 0) { current -= 1; goTo(tops[current]); }
            else { goTo(0); }
        };
        const onWheel = (e) => {
            // 스크롤 막고(네이티브), 우리가 애니메이션으로 이동
            e.preventDefault();
            if (isAnimating) return;
            const dy = e.deltaY || (e.wheelDelta ? -e.wheelDelta : 0) || 0;
            if (Math.abs(dy) < 2) return;
            if (dy > 0) next(); else prev();
        };
        // 터치 스와이프(옵션)
        let tsY = 0;
        const onTouchStart = (e) => { if (e.touches && e.touches[0]) tsY = e.touches[0].clientY; };
        const onTouchEnd = (e) => {
            if (isAnimating) return;
            const teY = (e.changedTouches && e.changedTouches[0]) ? e.changedTouches[0].clientY : tsY;
            const dist = teY - tsY;
            if (Math.abs(dist) < 40) return;
            if (dist < 0) next(); else prev();
        };
        // 리사이즈/로드/컨텐츠 변경(footer 높이 auto 포함)에 대응
        const debounce = (fn, d=100) => { let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn.apply(this,a), d); }; };
        const refresh = debounce(calcPos, 100);
        window.addEventListener('wheel', onWheel, { passive: false });
        window.addEventListener('touchstart', onTouchStart, { passive: true });
        window.addEventListener('touchend', onTouchEnd, { passive: true });
        window.addEventListener('resize', refresh, { passive: true });
        window.addEventListener('load', refresh, { passive: true });
        // footer 높이 auto 대응: 래퍼/푸터에 ResizeObserver (지원 브라우저에서)
        if (window.ResizeObserver) {
            const ro = new ResizeObserver(refresh);
            ro.observe(wrap);
            const foot = document.querySelector('#main .section.foot, .section.foot');
            if (foot) ro.observe(foot);
        }
        calcPos();


    }
};


if (getdevice() === 'pc') { main_fullpage.init(); }
window.addEventListener('resize', () => {
    if (getdevice() === 'pc') { main_fullpage.init(); }
});