웹에서 흔히 사용되는 인기 있는 인터랙션을 꼽자면, 단연 스크롤 애니메이션일 것입니다.
과거에는 jQuery 플러그인을 사용하여 간단히 추가하고 약간의 수정을 통해 원하는 기능을 구현하곤 했습니다. 하지만 이제는 jQuery 사용을 지양하는 추세라 활용하기가 쉽지 않습니다. 스크롤할 때 콘텐츠가 sticky 상태로 변하거나 특정 위치에서 애니메이션 효과를 주기 위해 과거에는 Skrollr나 ScrollMagic 같은 JavaScript 라이브러리를 주로 사용했습니다. 하지만 최근에는 GSAP(GreenSock Animation Platform)가 대세인 듯합니다.
GSAP로 전환된 지도 꽤 오래된 것 같은데, 어렵게 느껴져서 엄두를 내지 못하다가 이번에 마음을 다잡고 GSAP의 ScrollTrigger를 활용해 가로 스크롤 애니메이션을 구현해보았습니다.
사실 처음에는 국내에서 이를 구현한 사례나 코드 예제를 참고하려 했지만, 이해하기 쉽지 않았습니다. WAAPI 스크롤 기반 애니메이션을 사용한다면 CSS만으로 더 쉽게 구현이 가능하겠지만 아직 모든 브라우저에서 지원하지 않기 때문에 제가 이해할 수 있는 방식으로 정리하고 기록해보았습니다.
GSAP(+ScrollTrigger) 세팅하기
라이브러리 세팅은 GSAP 공식 가이드 내의 Installation을 통해 확인할 수 있습니다.
저는 정적인 페이지로 테스트 할거라 CDN 스크립트 태그를 삽입했습니다.
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/3.12.4/gsap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/3.12.4/ScrollTrigger.min.js"></script>
마크업 구조
전체를 감싸는 wrap안에 각 section이 세개가 들어가고, 두 번째 섹션에서 가로 스크롤을 구현하고자 합니다.
<div class="wrap">
<div class="section intro">
Section 01
</div>
<div class="section xScroll">
<p>Section 02</p>
<img src="https://dummyimage.com/800x400/000/fff">
<img src="https://dummyimage.com/800x400/000/fff">
<img src="https://dummyimage.com/800x400/000/fff">
<img src="https://dummyimage.com/800x400/000/fff">
<img src="https://dummyimage.com/800x400/000/fff">
</div>
<div class="section end">
Section 03<br>DONE!
</div>
</div>
CSS 세팅
.wrap {
overflow-x: hidden; // 가로 스크롤 제거
}
.section {
min-width: 100vw; // 뷰포트 최대 너비 지정
min-height: 100vh; // 뷰포트 최대 높이 지정
text-align: center;
font-size: 1.5rem;
font-weight: 700;
line-height: 1.4;
}
보통 가로 스크롤을 적용하는 페이지는 뷰포트에 꽉 차는 영역에 고정되고, 그 안에서 가로 콘텐츠가 스크롤되도록 구현합니다. 이를 위해 각 섹션의 최소 너비와 높이를 각각 100vw와 100vh로 설정하였습니다.
또한, 두 번째 섹션의 가로로 나열된 콘텐츠로 인해 페이지 전체에 가로 스크롤이 생기는 것을 방지하기 위해, div.section
을 감싸고 있는 div.wrap
의 가로 스크롤을 제거하였습니다.
두 번째 섹션은 콘텐츠가 가로로 스크롤 되도록 구현해야 하기 때문에, 섹션의 width를 콘텐츠 너비와 동일하게 설정하였습니다. 또한, 섹션 간의 구분을 명확히 하고, 콘텐츠를 보기 좋게 하기 위해 기본적인 스타일을 지정하며 CSS 세팅을 마무리하였습니다.
body {
margin: 0;
padding: 0;
}
.wrap {
overflow-x: hidden;
}
.section {
display: flex;
align-items: center;
text-align: center;
min-width: 100vw;
min-height: 100vh;
font-size: 1.5rem;
font-weight: 700;
line-height: 1.4;
}
/* 첫번째 섹션 */
.intro {
justify-content: center;
background: #f2f2f2;
}
/* 두번째 섹션 (가로 스크롤) */
.xScroll {
gap: 200px;
background: #ddd;
width: max-content;
}
.xScroll p {
display: flex;
align-items: center;
justify-content: center;
width: 800px;
height: 400px;
background: #fff;
margin-left: calc((100vw - 800px)/2);
}
.xScroll img:last-child {
margin-right: calc((100vw - 800px)/2);
}
.xScroll img {
width: 800px;
height: 400px;
}
/* 세번째 섹션 */
.end {
justify-content: center;
background: #000;
color: #fff;
}
CSS 적용 시 아래와 같이 나타납니다.
GSAP ScrollTrigger을 통한 가로 스크롤 적용하기
CDN 스크립트를 삽입하고 기본적인 HTML 및 CSS 마크업 작업을 마쳤다면, 이제 GSAP ScrollTrigger를 사용해 본격적으로 가로 스크롤을 구현해봅니다.
먼저 ScrollTrigger를 등록(선언) 해줍니다. 이후 ScrollTrigger 외에 GSAP의 다양한 플러그인을 사용할 경우에도 동일하게 gsap.registerPlugin()
안에 사용하려는 플러그인을 넣으면 됩니다. 여러 개를 등록할 때는 콤마(,)로 구분합니다.
gsap.registerPlugin(ScrollTrigger);
GSAP ScrollTrigger를 사용한 가로 스크롤은 실제 브라우저의 기본 가로 스크롤이 아닌, 세로 스크롤 영역을 확장하고, transformX
를 사용해 콘텐츠의 가로 위치를 조정하는 방식으로 구현됩니다. 이를 위해 먼저, 가로 콘텐츠를 감싸고 있는 두 번째 섹션(.xScroll
)의 가로 콘텐츠 길이를 구해야 합니다.
가로 콘텐츠 길이는 콘텐츠의 전체 길이에서 현재 뷰포트의 너비(브라우저 창 너비)를 빼서 계산할 수 있습니다. 이는 가로 콘텐츠가 뷰포트를 넘어서는 부분의 길이를 구하기 위함입니다.
// 가로 스크롤이 적용될 두 번째 섹션(.xScroll) 지정
const xScroll = document.querySelector(".xScroll");
// 콘텐츠 전체 길이(scrollWidth)에서 현재 뷰포트의 너비(window.innerWidth)를 뺀 값을 통해 가로 스크롤 시 이동해야 할 전체 길이 계산
const xScrollWidth = xScroll.scrollWidth - window.innerWidth;
이어서 GSAP와 ScrollTrigger 설정을 합니다.
gsap.to(xScroll, {
x: -xScrollWidth, // 콘텐츠가 가로로 이동(스크롤)되게끔 음수로 설정
ease: "none", // 애니메이션에 가속도 효과를 사용하지 않음
scrollTrigger: {
trigger: xScroll, // 트리거 요소 지정
start: "top top", // xScroll의 상단과 브라우저 상단이 맞닿을 때 애니메이션 시작
end: `+=${xScrollWidth}`, // xScroll의 콘텐츠 너비만큼 스크롤 후 애니메이션 종료
scrub: true, // 스크롤 진행에 따라 애니메이션이 동기화되어 진행
pin: true, // 스크롤 중에 요소를 고정하여 애니메이션을 실행
anticipatePin: 1, // 스크롤 트리거 요소의 위치를 예측하여 스크롤 시 튀는 효과 방지
markers: false, // 디버그용 마커 표시
}
});
두 번째 섹션인 xScroll
을 왼쪽으로 이동(가로 스크롤)시키기 위해, gsap.to()
를 사용하여 xScroll
을 지정하고 x
값을 음수로 설정합니다. (양수일 경우 우측으로 이동합니다.)
그 후, ScrollTrigger
플러그인을 설정합니다. xScroll
영역에 진입하면 가로 스크롤이 되어야 하므로, xScroll
을 트리거로 지정합니다.
start
와 end
는 각각 애니메이션의 시작 지점과 끝 지점을 설정하는 역할을 하며, 트리거 요소의 기준 위치와 뷰포트(브라우저 화면)의 기준 위치 두 가지 값으로 동작합니다.
예를 들어, div.xScroll
에 start: "top center"
를 지정했다고 가정한다면, div.xScroll
의 상단이 브라우저의 중앙에 위치할 때 트리거가 시작된다고 이해할 수 있습니다.
end
에는 div.xScroll
가 총 가로 스크롤 길이만큼 세로로 스크롤 시 종료되게끔 하기 위해 +=${xScrollWidth}
를 입력합니다.
scrub
, pin
, anticipatePin
, markers
는 위 코드에 함께 기재한 주석에 달린 설명으로 대체합니다. scrub
, pin
, anticipatePin
은 GSAP ScrollTrigger로 가로 스크롤을 구현할 때 기본값 그대로 사용한다고 보시면 됩니다.
결과물
이렇게 가로 스크롤 구현이 끝났습니다. 그런데 글 초반에 삽입된 구동 영상과 달리 뭔가 빠진 부분이 있는 것 같습니다.
스크롤이 끝나면 div.xScroll
배경색이 바뀌고, 이후 스크롤 시 뒤에 있는 세 번째 섹션(div.end
)이 고정된 채로 드러나는 부분이 빠졌네요.
조금 기교를 부려봤습니다. scrollTrigger
의 onUpdate
속성을 활용해 애니메이션의 시작과 끝을 파악하고, 배경색 변화를 주었습니다. 배경색 변경 외에도 다양한 디자인 효과나 애니메이션을 적용할 수 있습니다.
세 번째 섹션(div.end
)이 스크롤 시 고정된 채로 드러나는 부분은 position
과 z-index
를 이용해 처리했습니다. 이 부분은 글을 읽으시는 분이 아래 GitHub에 호스팅된 링크를 통해 직접 확인해 보실 수 있도록 재미(?) 요소로 남겨두겠습니다.
이제 구현이 끝났으니, 여러분도 직접 스크롤을 시도해 보세요! (그럼 뭔가가 변할 거예요!)