inflearn logo
강의

Khóa học

Chia sẻ kiến thức

Hướng dẫn GSAP dành cho hoạt hình trên web Phần 03

Bố cục OneScroll (1)

OneScroll Layout (1) 강의중 질문 드립니다.

Đã giải quyết

270

mnsk8743

2 câu hỏi đã được viết

1

안녕하세요, 쉬운 강의 덕분에 gsap 을 쉽게 접하고 사용할 수 있어서 감사합니다.
근데 해당 부분 구현중에 범샘께서 작성하신 코드(ScrollTrigger-finished)에서 확인해보면

1) section3 도달 후, section2로 스크롤을 올리는 경우
2) section4 도달 후, section3에서 다시 section4 로 스크롤을 내리는 경우
해당 2가지 경우에서 section03을 기준으로 스크롤이 위아래로 작동을 잘 하지 않습니다.

if(currentPageIndex === 3) return;
때문인 것 같은데, 스크롤을 자유롭게 올렸다 내렸다 하면서 페이지를 확인할 수 있도록 하려고 하는데 잘 안되네요.
코드를 어떤 방식으로 수정해야할지 질문 남깁니다.

감사합니다.

javascript 애니메이션 인터랙티브-웹 gsap scrolltrigger

Câu trả lời 1

1

kindtiger

안녕하세요 김민식님 😀

 

질문주신 문제를 확인 했으며, 코드에 대한 설명은 아래와 같습니다.

 

  1. section3 도달 후, section2로 스크롤을 올리는 경우

원인 : section03 진입시 state.isPlaying값이 true로 변경되어 wheel 이벤트는 작동되지만
handleWheel 이벤트 안에서의 조건 처리 인 if(state.isPlaying) 부분에 조건이 적용되지 않아 바로 올라가지 않는 것 입니다.

 

해결방안 :

일반 스크롤을 조금이라도 내린 후 올리면 다시 isPlaying의 상태 변수가 true로 되기 때문에 제대로 동작할 수 있습니다.

우선, 언급해 주신 if(currentPageIndex === 3) return 조건을 else 안으로 넣어 애니메이션이 작동되도록 설정합니다.

 if(state.isPlaying){
    
    state.isPlaying = false;


    if(direction === 'up'){
      if(currentPageIndex <= 1) return;
      --currentPageIndex
      
  
    }else{
      if(currentPageIndex === 3) return;  // 해당 부분 입니다.
      if(currentPageIndex >= sections.length) return;
      ++currentPageIndex
    }
    transition(currentPageIndex,direction)
    
  }

그리고 다음 문제를 해결하기 위해선 isPlaying이 false인과 동시에 휠의 방향이 up인 조건이 필요하며,
handleWheel 안에 조건을 더 만들어 처리해줄 수 있습니다.

 

  if(!state.isPlaying && direction === 'up'){
    
    if(!state.isGoingUp){
      transition(2,'up')
      state.isGoingUp = true;
    }
    return;
  }

이후 해당 조건 발동시 아래의 코드가 읽히지 않도록 return을 넣어 주고(아래의 코드는 isPlaying이 true인 경우 애니메이션이 실행되는 조건며, transition함수는 실행 즉시 isPlaying을 true로 만들어 버립니다.) 애니메이션이 1회만 실행할 수 있도록(wheel 이벤트는 여러번 실행되기 때문에 1번만 실행될 수 있는 변수가 필요합니다.) isGoingUp변수를 만들고 제어해줍니다.

이어서 transition 함수 안의 scrollTrigger onComplete callback 부분에 section03 영역에 다시 진입시 isGoingUp 변수를 false로 만들어주어 다음번에 같은 섹션 재진입시 (section02 → section03) 동일한 애니메이션을 가져갈 수 있도록 처리해줍니다.

 

    onComplete:()=>{
      state.isPlaying = true;
      
      globalEnter()

      switch (index) {
        case 1: page01.enter(); return;
        case 2: page02.enter(); return;
        case 3: 
          page03.enter(); 
          state.isGoingUp = false;
          return
        ;
        case 4: page04.enter(); return;
      
      }
    }

 

  1. section4 도달 후, section3에서 다시 section4 로 스크롤을 내리는 경우

원인 : section04 도달 후 지속적인 down wheel을 할 경우 isPlaying의 상태가 false이기 때문에 더이상 애니메이션이 동작하지 않음.

 

해결방안 :

section04에 도착할 경우 휠을 계속 진행하여도 isPlaying의 상태를 true로 유지시켜 주어야 합니다.
그리고 section01에서도 위로 계속 휠을 할 경우 동일한 문제가 발생하기 때문에 handleWheel 함수 안에서 같이 조건처리를 진행합니다.


function handleWheel(e){

  let direction = e.deltaY < 0 ? 'up' : 'down'

  // 아래 코드의 2줄의 조건 처리 부분 입니다.
  if(direction === 'up' && currentPageIndex === 1) return; 
  if(direction === 'down' && currentPageIndex === sections.length) return;

  if(!state.isPlaying && !state.isScrolling && direction === 'up'){
    
    if(!state.isGoingUp){
      transition(2,'up')
      state.isGoingUp = true;
    }
    return;
  }

  if(state.isPlaying){
    
    state.isPlaying = false;


    if(direction === 'up'){
      if(currentPageIndex <= 1) return;
      --currentPageIndex
      
      
  
    }else{
      if(currentPageIndex === 3) return;
      if(currentPageIndex >= sections.length) return;
      ++currentPageIndex
    }
    transition(currentPageIndex,direction)
    
  }
}

위에서 return을 해주셔야 아래의 playing을 변경하는 코드까지 도달하지 않으므로 위에서 조건처리를 진행합니다.

 

이후 section03에서도 스크롤을 할 경우 자잘하게 section의 이동이 생기는 문제를 해결하기 위해 확실하게 스크롤 중인 상태냐 아니냐에 따라 추가 조건을 넣어 문제를 해결합니다.

 

전역 상태는 다음과 같습니다.

const state = {
  isPlaying: true,
  isScrolling: false,
  isGoingUp: false
}

 

 

scrollTrigger onUpdate callback 부분에 progress를 가져와 0인 상태에서만 isScrolling이 false가 될 수 있도록 (스크롤 제일 최상단 부분) 설정해줍니다.

  page03:{
    enter:()=>{
      // console.log('enter page03');

      if(!ScrollTrigger.getById('section03')){

        ScrollTrigger.create({
          trigger: '.depth_wrapper',
          start: 'top top',
          end: 'bottom bottom',
          markers: true,
          id:'section03',
          onLeaveBack:()=> {
            if(!state.isScrolling){
              transition(2,'up')
            }
          },
          onLeave:()=> transition(4,'down'),
          onUpdate:({progress}) => {
            // 해당 부분 입니다.
            // onUpdate 함수는 매개변수가 있기 때문에 progress만 구조분해로 가져옵니다.
            if(progress === 0) {
              state.isScrolling = false;
            }else{
              state.isScrolling = true;
            }
            
          }
        })
  
        markers()
      }
     

    },
    leave:()=>{
      // console.log('leave page03');
    }
  },

 

 

이렇게 3가지 옵션을 설정하면 보다 안정적으로 oneScroll 이벤트와 중간에 연결된 normal scroll의 레이아웃까지 mixin하여 사용할 수 있습니다.

 

완성된 코드는 다음과 같습니다.


const state = {
  isPlaying: true,
  isScrolling: false,
  isGoingUp: false
}


let currentPageIndex = 1;


const sections = gsap.utils.toArray('.section');


const pages = {
  page01:{
    enter:()=>{
      // console.log('enter page01');
    },
    leave:()=>{
      // console.log('leave page01');
    },
  },
  page02:{
    enter:()=>{
      // console.log('enter page02');
    },
    leave:()=>{
      // console.log('leave page02');
    }
  },
  page03:{
    enter:()=>{
      // console.log('enter page03');

      if(!ScrollTrigger.getById('section03')){

        ScrollTrigger.create({
          trigger: '.depth_wrapper',
          start: 'top top',
          end: 'bottom bottom',
          markers: true,
          id:'section03',
          onLeaveBack:()=> {
            if(!state.isScrolling){
              transition(2,'up')
            }
          },
          onLeave:()=> transition(4,'down'),
          onUpdate:({progress}) => {

            
            if(progress === 0) {
              state.isScrolling = false;
            }else{
              state.isScrolling = true;
            }
            
          }
        })
  
        markers()
      }
     

    },
    leave:()=>{
      // console.log('leave page03');
    }
  },
  page04:{
    enter:()=>{
      // console.log('enter page04');
    },
    leave:()=>{
      // console.log('leave page04');
    }
  },
}


function globalEnter(){
  // console.log('globalEnter');
  gsap.to('h2',{opacity:1,y:0})
  
}

function globalLeave(){
  // console.log('globalLeave');
  gsap.to('h2',{opacity:0,y:30})
}

function transition(index,dir){

  const {page01,page02,page03,page04} = pages;

  currentPageIndex = index;


  gsap.to('.wrapper',{
    y: -innerHeight * (index - 1),
    duration:1.5,
    ease:'expo.inOut',
    onStart:()=>{

      globalLeave()

      switch (dir === 'up' ? index + 1 : index - 1) {
        case 1: page01.leave(); return;
        case 2: page02.leave(); return;
        case 3: page03.leave(); return;
        case 4: 
          page04.leave(); 
          state.isPlaying = false;
          return;
      }
    },
    onComplete:()=>{
      state.isPlaying = true;
      
      globalEnter()

      switch (index) {
        case 1: page01.enter(); return;
        case 2: page02.enter(); return;
        case 3: 
          page03.enter(); 
          state.isPlaying = false;
          state.isGoingUp = false;
          return
        ;
        case 4: page04.enter(); return;
      
      }
    }
  })
}


function handleWheel(e){

  let direction = e.deltaY < 0 ? 'up' : 'down'


  if(direction === 'up' && currentPageIndex === 1) return;
  if(direction === 'down' && currentPageIndex === sections.length) return;

  if(!state.isPlaying && !state.isScrolling && direction === 'up'){
    
    if(!state.isGoingUp){
      transition(2,'up')
      state.isGoingUp = true;
    }
    return;
  }

  if(state.isPlaying){
    
    state.isPlaying = false;


    if(direction === 'up'){
      if(currentPageIndex <= 1) return;
      --currentPageIndex
      
      
  
    }else{
      if(currentPageIndex === 3) return;
      if(currentPageIndex >= sections.length) return;
      ++currentPageIndex
    }
    transition(currentPageIndex,direction)
    
  }
}


container.addEventListener('wheel',handleWheel)





markers()

 

자세하게 확인해볼 수 있도록 수업 자료에 (scrollTrigger-finished) 업데이트 해두겠습니다.

image.png

 

 

 

열정적으로 수업을 들어주셔서 감사합니다 :)

 

 

 

imagesLoaded에 관한 질문

0

19

2

섹션04 Layout에서 Mixed Layout파트의 실습 index.html파일 열었을때 선생님께서 보여주시는 가로스크롤이 안나타남.

0

88

3

안녕하세요 ScrollSmoother에 대해 질문드릴게요

1

205

2

GSAP 플러그인의 무료화 관련

2

227

3

barba와 ScrollSmoother 사용했을 때 스크롤 업데이트

0

111

2

barba.js 사용시 페이지이동

1

208

2

이상한 부분이 있어서 문의드립니다.

1

104

1

smooth-scrollbar 관련 질문

1

228

2

imagesLoaded 에 관해 질문드려요

1

108

1

scroll Draw SVG에서 실선이 아닌 점선으로 그리고 싶어요

1

187

1

파트 4 오픈 일정 문의

1

146

2

GSAP을 사용하면서 리사이징 시 애니메이션 값 재할당에 대해 질문드립니다.

1

192

2

스무스 스크롤바 모바일에서 뻑뻑한 느낌이 들어요!

1

248

2

OneScroll Layout 질문 드립니다!

1

165

2

OneScroll Layout (1) 관련 재질문드립니다.

2

167

1

이미지 엑박

1

190

1

UI관련 문의드립니다..!

1

239

2

OneScroll Layout 모바일 터치

1

472

2

노션 링크 보는 곳

1

388

1

반응형 관련해서 질문드립니다.

1

560

2

ScrollTrigger의 end와 toggleClass

1

949

1

Text Effects(2)는 Text Effects(1)영상에 포함돼있는 것 같아요!

1

269

1

nav위에 마우스를 올리면 스크롤이 안돼요 😢

1

493

2

ScrollTrigger의 animation에 함수호출

1

452

1