https://velog.io/@gosoomdoce/gsap.context-와-return-.revert-해야하는-이유
이 글에서 연계
<script>
// section 3의 active slider의 scale을 크게 해버리기
function letActiveHorny() {
let ctx = gsap.context(() => {
let myLack = document.querySelector('#forgiveMyLack');
let float_div = document.querySelectorAll('.swiper-float-div');
let active_article = document.querySelector('.swiper-slide-active');
let notActiveSlide = document.querySelectorAll('.swiper-slide:not(.swiper-slide-active)');
let fill = document.querySelector('.swiper-slide.swiper-slide-active .fill');
let lack = document.querySelectorAll('.lackness');
let gimmick = document.querySelector('.swiper-gimmick');
let marquees = document.querySelector('.sec3_products_marquee_wrapper');
let goDownArticle = document.querySelector('.big_products_swiper_wrapper');
// scrub을 사용하고, swiper를 연동해서 쓰기 때문에, 적어 놓은 대상들에게 kill로 중첩 방지.
gsap.killTweensOf(".swiper-slide, .lackness, .big_products_swiper_wrapper, .swiper-float-div, .swiper-gimmick, .sec3_products_marquee_wrapper");
// swiper 될 때 마다 스크롤 트리거 위치가 바뀔 염려가 있으므로, 스크롤 트리거 중첩 방지를 위해 kill함.
["BigToSmall", "SmallToNormal", "goDownFill"].forEach(id => {
let trigger = ScrollTrigger.getById(id);
if (trigger) trigger.kill();
});
function animateFill() {
// 5 → 3
let bigToSmall = gsap.timeline({
scrollTrigger: {
trigger: myLack,
start: 'top bottom',
end: 'bottom top',
scrub: 0.5,
markers: false,
id: 'BigToSmall',
lazy: false,
}
});
bigToSmall.fromTo(
lack,
{ scaleX: 5, scaleY: 3 },
{ scaleX: 3, scaleY: 3 }
);
// 3 → 1
let smallToNormal = gsap.timeline({
scrollTrigger: {
trigger: myLack,
start: 'bottom+=10px top',
end: '+=1500',
scrub: 0.5,
markers: false,
id: 'SmallToNormal',
lazy: false,
}
});
smallToNormal.fromTo(
lack,
{ scaleX: 3, scaleY: 3 },
{ scaleX: 1, scaleY: 1 }
);
// transform
let goDownFill = gsap.timeline({
scrollTrigger: {
trigger: myLack,
start: 'bottom+=1450 top',
end: '+=100',
scrub: 0.5,
markers: false,
id: 'goDownFill',
lazy: false,
}
});
goDownFill.to(goDownArticle, {
top: '75%',
yPercent: -75, //gsap에서는 yPersent를 쓰면 자기 자신 크기 기준으로 퍼센트란 뜻.
ease: 'none',
})
}
function letSlideVisible() {
let slideVisible = gsap.timeline();
slideVisible.fromTo(
gimmick,
{ opacity: 1 },
{ opacity: 0,}
);
ScrollTrigger.create({
animation: slideVisible,
trigger: myLack,
start: 'bottom+=800px top',
end: '+=1200',
scrub: 0.5,
markers: false,
});
}
function swiperFloatDivHide() {
let swiperFloatDivHide = gsap.timeline();
swiperFloatDivHide.fromTo(float_div,
{ opacity: 0 },
{ opacity: 1, duration: 0.1,}
);
ScrollTrigger.create({
animation: swiperFloatDivHide,
trigger: myLack,
start: 'bottom+=1250px top',
markers: false,
toggleActions: 'play none none reverse',
onEnter: () => {
active_article = document.querySelector('.swiper-slide-active');
active_article.classList.add('hover-enabled'); // hover 효과를 활성화하는 클래스 추가
},
onLeaveBack: () => {
active_article = document.querySelector('.swiper-slide-active');
active_article.classList.remove('hover-enabled'); // 스크롤이 역방향일 때 클래스 제거
}
});
}
function marqueeDivHide() {
let swiperFloatDivHide = gsap.timeline();
swiperFloatDivHide.fromTo(marquees,
{ opacity: 0 },
{ opacity: 1, duration: 1.5,}
);
ScrollTrigger.create({
animation: swiperFloatDivHide,
trigger: myLack,
start: 'bottom+=1600px top',
markers: false,
toggleActions: 'play none none reverse',
});
}
swiperFloatDivHide();
letSlideVisible();
animateFill();
marqueeDivHide();
});
return () => ctx.revert();
}
</script>
gsap.context()를 사용하면 해당 컨텍스트에서 실행된 모든 애니메이션을 추적할 수 있다.
즉, ctx.revert()를 호출하면, 그 컨텍스트에서 실행된 애니메이션만 제거할 수 있다.
하지만, ctx.revert()만으로는 부족한 경우가 있음
ctx.revert()는 해당 context() 내에서 실행된 애니메이션만 제거하기 때문에,
페이지 전체에 걸쳐 실행되는 애니메이션 (예: Swiper, ScrollTrigger)까지 제거하지 못함.
scrub: true가 설정된 애니메이션이 여러 번 실행될 경우, 중복 실행될 수 있음.
그래서 별도로 gsap.killTweensOf()와 ScrollTrigger.kill()을 추가해야 함.
target에서 실행중인 모든 트윈 애니메이션을 제거함
특히 scrub: true 상황에서는 스크롤에 감응하여 애니메이션이 실행되므로 중복 실행 가능성 존재.
예를 들어 swiper와 연동했을 떄 active가 바뀌는데, 이 때 이전 애니메이션이 남아 있으면 성능 저하 우려가 있음 > 그것을 제거.
즉, gsap.context()가 있더라도 gsap.killTweensOf()를 함께 써야 하는 이유는?
context()는 특정 컨텍스트에서 실행된 애니메이션을 관리할 수 있지만,
이미 실행된 애니메이션(Tween)이 계속 남아 있을 수 있음.
gsap.killTweensOf()를 사용하면, 이전 애니메이션을 모두 삭제하고 새로운 애니메이션만 실행되도록 보장할 수 있음.
ScrollTrigger.getById(id).kill()는 해당 ID를 가진 ScrollTrigger를 찾아서 제거하는 역할을 함.
ScrollTrigger는 한 번 생성되면 스크롤 이벤트를 계속 감지하기 때문에,
중복된 ScrollTrigger가 남아 있으면 애니메이션이 여러 번 실행될 수 있음.
특히, Swiper 같은 동적인 UI에서는 요소가 변경될 때마다 ScrollTrigger가 다시 등록될 가능성이 큼.
→ 그래서 ScrollTrigger.kill()을 사용하여 기존 트리거를 삭제하고 새로 등록해야 함.
gsap.context()를 사용하여 특정 컨텍스트 내에서 애니메이션 실행
ctx.revert()를 사용하여 컨텍스트 내의 애니메이션을 정리
gsap.killTweensOf()를 사용하여 중복 실행되는 트윈 애니메이션 제거
ScrollTrigger.getById().kill()을 사용하여 중복된 ScrollTrigger 방지
=> 이는 swiper 같은 동적 요소나, react처럼 페이지가 바뀌는 요소들과 결합되었을 때 더 빛남
내 사이트에서의 구조
이런식으로 개별 함수로 묶음
그 안에서 context()로 한번 더 묶음.
그 후 개별함수 안에서 return () 실행
context() 안에서 추가 중복 실행 방지가 필요하다 판단되는 함수들에는 추가로
gsap.killTweensOf() 혹은 scrollTrigger.getById().kill() 기능 추가,
gsap 애니메이션 한번에 관리