강의

멘토링

커뮤니티

Cộng đồng Hỏi & Đáp của Inflearn

Hình ảnh hồ sơ của mnsk8743
mnsk8743

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

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

Viết

·

251

·

Đã chỉnh sửa

1

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

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

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

감사합니다.

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

Câu trả lời 1

1

kindtiger님의 프로필 이미지
kindtiger
Người chia sẻ kiến thức

안녕하세요 김민식님 😀

 

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

 

  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

 

 

 

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

 

 

 

Hình ảnh hồ sơ của mnsk8743
mnsk8743

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

Đặt câu hỏi