Skip to content
Ethan Sup's log
Github

CSS Scroll snap으로 fullpage.js를 대체하고 싶었다

Devlog3 min read

홈페이지 개발을 진행하는데 메인페이지를 어떻게 디자인할까 고민하다가 스크롤을 약간만 해도 스크롤이 부드럽게 움직여서 고정되는 기술을 site of the day의 어떤 페이지에서 본 기억을 가지고 그것을 구현해보려고 했다. 하지만 느낌으로 찾기는 어려웠고 검색에 검색을 거듭해서 결국 찾았다.

구현하고 싶었던 것 구현하고 싶었던 Scroll snap(fullpage.js)

처음 봤었던 fullpage.js는 구현하고 싶었던 것 그 자체였다. 쓰기 쉽고, 최근까지도 커밋내역이 있을 정도로 활발한 오픈소스이기 때문에 장점은 충분했다.

하지만 단점 또한 존재했다. 첫번째로 jQuery로 만들어졌다는 것이 문제였다. Gatsby로 구현중이였기 때문에 정적페이지로 빌드되므로 패키지의 크기가 작으면 작을 수록 좋은데 fullpage.jsjQuery로 만들어져있기 때문에 패키지의 크기가 생각한 것보다 커질 수 있다. (그리고 개인적으로 jQuery를 싫어하기때문에..)

fullpage.js pricing 음.....어....

두번째로는 오픈소스임에도 상업적 목적으로 사용하려면 유료로 돈을 내야하는 점이 있다. 물론 이것을 구현하기 위해 살 수도 있지만 첫 페이지에만 쓰는데 라이선스를 사는 것이 불필요한 것 같아서 결국 다른 것으로 대체하기로 결정했다.

다시 검색하던 와중에 이전 Mozilla Developer RoadShow에서 본 snap-scroll-type이 생각났다. 해당 css를 구현한 것을 봤을 때 내가 원하는 대로 돌아갔던 것을 확인했었고, CSS로만 구현하기 때문에 여타 다른 라이브러리가 필요하지 없고 비용을 지불할 필요도 없기 때문에 CSS로 구현하는 것을 택했다.

can I use snap-scroll? snap-scroll은 중요한 브라우저에는 모두 구현되어있다(IE 빼고)

TL;DR - 그래서?

한 줄로 요약하자면..

후.. 너네는 이런거 쓰지마라...

사람들이 안 쓰는데 이유가 있다

CSS의 snap-scroll은 Firefox에서는 정상적으로 돌아갔다. 약간만 내려도 다음 div로 고정이 되면서 스크롤이 넘어갔다. 애니메이션도 적절하고 프레임도 매끄럽게 흘러갔다(끊기지 않는다는 뜻). 정말 완벽했다.

후.. 너네는 이런거 쓰지마라...

크롬의 통수(왼쪽: Firefox 78, 오른쪽: Chrome 84)

하지만 Chrome을 간과하고 있었다. 개발을 끝마치던 중, Chrome을 키고 테스트를 하자 Firefox의 애니메이션과는 다르게 내가 스크롤 하는게 끝나고 그 다음 애니메이션이 시작하기 전까지의 지연되는 시간이 인지할 만큼 긴 것을 확인할 수 있었다.. 개발은 모두 끝났는데...

그래서 이번 포스트의 제목이 CSS로 fullpage.js를 대체하기가 아닌 삽질기가 된 이유는 크롬에서 테스트를 하지 않고 개발을 완료한 글쓴이의 멍청함때문이다. 이제 CSS의 Scroll snap관련 속성들을 간단하게 소개하고 어떻게 적용했는지에 대해 쓰려고 한다.

CSS Scroll snap

Scroll snap에 관련된 속성은 크게 네가지가 있다.

  • Scroll container(overflow: scroll인 컨테이너)에 넣는 속성
    • snap-scroll-type
    • snap-scroll-padding
  • 자식 요소에 넣는 속성
    • snap-scroll-align
    • snap-scroll-margin

snap-scroll-type

1snap-scroll-type: none | [ x | y | block | inline | both ] [ mandatory | proximity ]?

snap-scroll-type은 컨테이너에서 Scroll snap을 할 축과 엄격도를 정한다. 첫번째에 넣는 값으로는 x, y축 혹은 block축, inline축 등으로 정할 수 있다. 두번째로는 mandatory를 넣어 항상 snap point에 놓이도록 무조건 Scroll snap이 되도록 할 수 있고 proximity를 넣어 사용자의 스크롤을 우선시할 수 있다.

mandatory와 proximity의 차이

mandatory와 proximity의 차이(좌: mandatory, 우: proximity)

snap-scroll-align

1scroll-snap-align: [ none | start | end | center ]{1,2};

snap-scroll-align은 Scroll container의 자식요소가 어디서 snap해야 하는지 알려준다. Scroll container의 viewport에서 해당 축을 기준으로 어디에서 snap해야할지 알려준다. none인 경우 snap하지 않고, start/center/end는 자식요소에서 snap할 위치를 축을 기준으로 맨 앞/중간/맨 뒤로 설정한다.

snap-scroll-align의 차이

snap-scroll-align start/center/end의 차이

scroll-margin/padding

1scroll-margin: [ <length-percentage> | auto ]{1,4};
2scroll-padding: [ <length-percentage> | auto ]{1,4};

Scroll container의 viewport에서 snap하는 곳에서 \<length-percentage>만큼 띄우도록 한다. padding은 모든 자식요소에 적용되지만 margin은 개별 자식요소에 적용된다.

이제 Scroll snap의 모든 속성을 알게 되었으니 header가 있는 메인페이지를 만들어보자.

시도1 : container에 snap-scroll 넣기

먼저 제목에도 적혀있듯이 순수 HTML과 CSS로 진행한 것이 아니라 React 프레임워크에서 진행했다(위에서 봤듯이 Chrome은 애니메이션이 이상하므로 테스트는 Firefox에서 진행했다). 그래서 스크롤할 내용물의 부모가 body태그가 아닌 div이기 때문에(body태그에 바로 렌더링할 수 있지만 권장하지 않는다고 한다.) 그래서 처음 한 시도는 snap container를 새로 만들어서 스크롤할 내용물을 넣는 것이였다.

1import React from 'react';
2import './App.css';
3
4function App () {
5 return (
6 <>
7 <header className="header">
8 header
9 </header>
10 <main className="scroll-container">
11 <div style={{backgroundColor: 'red'}}>1</div>
12 <div style={{backgroundColor: 'blue'}}>2</div>
13 <div style={{backgroundColor: 'orange'}}>3</div>
14 <div style={{backgroundColor: 'green'}}>4</div>
15 <div style={{backgroundColor: 'yellow'}}>5</div>
16 </main>
17 </>
18 )
19}
1header.header {
2 width: 100%;
3 height: 100px;
4 line-height: 100px;
5 text-align: center;
6 position: absolute;
7 background-color: black;
8 opacity: 0.5;
9 color: white;
10 font-size: 50px;
11}
12
13main.scroll-container {
14 height: 100vh;
15 scroll-snap-type: y mandatory;
16 scroll-padding-top: 100px;
17 overflow-y: scroll;
18}
19
20main.scroll-container > div {
21 scroll-snap-align: start;
22 border-top: 1px solid red;
23 height: 700px;
24}

header공간을 넣기 위해서 scroll-padding-top값을 넣어 자식요소와 header가 겹치지 않게 해놓았다. 그리고 scroll-snap-type: y mandatory;를 Scroll container에 넣고 scroll-snap-align: start;를 자식요소에 넣어 최대한 fullpage.js와 비슷하게 만들었다.

시도1의 결과

시도1의 결과(Firefox 78)

Firefox에서 작동은 잘 되지만 header가 스크롤 위에 있어 만약 header가 opacity: 0.5;속성이 없다면 보기에 안 좋아지므로 다른 시도를 하기로 했다.

시도2: html에 snap-scroll 넣기

header가 스크롤 위에 가지 않도록 html 태그의 스크롤을 활용해서 Scroll Snap을 구현하는 것을 시도해보았다(html 태그의 스크롤은 vw에 포함되지 않기 때문에 100%를 하면 width에 스크롤을 제외한 값이 들어간다).

1import React from 'react';
2import './App.css';
3
4function App () {
5 return (
6 <>
7 <header className="header">
8 header
9 </header>
10 <main>
11 <div style={{backgroundColor: 'red'}}>1</div>
12 <div style={{backgroundColor: 'blue'}}>2</div>
13 <div style={{backgroundColor: 'orange'}}>3</div>
14 <div style={{backgroundColor: 'green'}}>4</div>
15 <div style={{backgroundColor: 'yellow'}}>5</div>
16 </main>
17 </>
18 )
19}
1html {
2 height: 100vh;
3 scroll-snap-type: y mandatory;
4 scroll-padding-top: 100px;
5 overflow-y: scroll;
6 }
7
8 header.header {
9 width: 100%;
10 height: 100px;
11 line-height: 100px;
12 text-align: center;
13 position: fixed;
14 background-color: black;
15 opacity: 0.5;
16 color: white;
17 font-size: 50px;
18 }
19
20 main > div {
21 scroll-snap-align: start;
22 border-top: 1px solid red;
23 height: 700px;
24 }

Firefox는 스크롤과 header가 분리되어있고, Scroll Snap도 정상적으로 작동하였다. 하지만...

Chrome의 통수2

또 Chrome의 통수...(왼쪽: Firefox 78, 오른쪽: Chrome 84)

Chrome이 문제가 되었다. Chrome에서 테스트했을 때 스크롤 할 때마다 여러개의 자식요소들이 한꺼번에 지나가는 현상을 볼 수 있었다. 그래서 이 시도도 실패로 돌아갔다.

시도 3: container에 snap-scroll 넣기 + 모든 스크롤 바 없애기

그래서 시도1의 코드에서 Snap container의 스크롤 바를 없애는 방식으로 스크롤 위에 header가 생기는 것을 방지하기로 했다.

1import React from 'react';
2import './App.css';
3
4function App () {
5 return (
6 <>
7 <header className="header">
8 header
9 </header>
10 <main className="scroll-container">
11 <div style={{backgroundColor: 'red'}}>1</div>
12 <div style={{backgroundColor: 'blue'}}>2</div>
13 <div style={{backgroundColor: 'orange'}}>3</div>
14 <div style={{backgroundColor: 'green'}}>4</div>
15 <div style={{backgroundColor: 'yellow'}}>5</div>
16 </main>
17 </>
18 )
19}
1header.header {
2 width: 100%;
3 height: 100px;
4 line-height: 100px;
5 text-align: center;
6 position: absolute;
7 background-color: black;
8 opacity: 0.5;
9 color: white;
10 font-size: 50px;
11}
12
13main.scroll-container {
14 height: 100vh;
15 scroll-snap-type: y mandatory;
16 scroll-padding-top: 100px;
17 overflow-y: scroll;
18 scrollbar-width: none;
19 -ms-overflow-style: none;
20}
21
22main.scroll-container::-webkit-scrollbar {
23 width: 0;
24 background-color: transparent;
25}
26
27main.scroll-container > div {
28 scroll-snap-align: start;
29 border-top: 1px solid red;
30 height: 700px;
31}

scrollbar-widthnone으로 두어 firefox의 스크롤 바를 없애고, -ms-overflow-style의 값도 none으로 두어 IE의 스크롤바도 없애준다(물론 의미는 없지만...). 마지막으로 -webkit-scrollbar의사요소의 값을 변경해 다른 모든 브라우저의 스크롤바를 없앤다.

결과

하긴 했는데..(왼쪽: Firefox 78, 오른쪽: Chrome 84)

그 결과 그나마 만족한 결과가 나왔다. header가 스크롤을 가리지 않고, 애니메이션이 매끄럽게 흘러간다(Chrome은 덜 매끄럽지만).

결론

내가 생각했을 때 CSS scroll snap를 사용했을 때 장단점은 다음과 같다.

장점

  1. JQuery를 사용하지 않고 페이지 스크롤을 사용할 수 있다.
  2. CSS는 무료다
  3. fullpage.js만큼 사용하기 쉽다!

단점

  1. 아직도 부족한 브라우저별 애니메이션 지원(이 때문에 쓰지 않는 듯하다)
  2. IE이 지원을 하지 않는다..

물론 fullpage.js를 사용했다면 쉽게 매끄러운 애니메이션을 구현할 수 있었겠지만 CSS로 한번 구현해보고 싶었다. Chrome 84버전에서는 덜 매끄러운 애니메이션이 나오면서 fullpage.js의 대체를 하지 못했지만 이후 버전에서는 애니메이션을 개선하여 대체하는 때가 왔으면 좋겠다.

적어도 이 소리는 안 나왔으면 좋겠다 적어도 이 소리는 안 나왔으면 좋겠다.

이 글을 마치며 CSS Snap Scroll으로 만든 결과물이 어떻게 됐냐고 물으신다면 fullpage.js을 사서 적용하였다..

© 2023 by Ethan Sup's log. All rights reserved.
Theme by LekoArts