해결된 질문
작성
·
112
·
수정됨
1
안녕하세요, 쉬운 강의 덕분에 gsap 을 쉽게 접하고 사용할 수 있어서 감사합니다.
근데 해당 부분 구현중에 범샘께서 작성하신 코드(ScrollTrigger-finished)에서 확인해보면
1) section3 도달 후, section2로 스크롤을 올리는 경우
2) section4 도달 후, section3에서 다시 section4 로 스크롤을 내리는 경우
해당 2가지 경우에서 section03을 기준으로 스크롤이 위아래로 작동을 잘 하지 않습니다.
if(currentPageIndex === 3) return;
때문인 것 같은데, 스크롤을 자유롭게 올렸다 내렸다 하면서 페이지를 확인할 수 있도록 하려고 하는데 잘 안되네요.
코드를 어떤 방식으로 수정해야할지 질문 남깁니다.
감사합니다.
답변 1
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;
}
}
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) 업데이트 해두겠습니다.
열정적으로 수업을 들어주셔서 감사합니다 :)