프론트엔드 개발을 하다보면 아래와 같은 페이지 형식을 자주 접할 수 있다.
일정부분까지 스크롤이 되었을 때 자연스럽게 다음 화면으로 스크롤되는 기능이다. 필자도 회사에서 맡은 프로젝트의 메인페이지가 위와 같은 형식으로 디자인되어 있었다. 디자인이 되어 있다는 건.. 내가 구현해야한다는 뜻이다 🤣(
귀찮은 기능이지만 월급 받으니까 해야지)
1. react-full-page 라이브러리
가장 먼저 시작한건 라이브러리 검색이었다.
(
코드 리팩토링, 기능 구현 등등 사내 프론트엔드 개발자가 혼자라.. 사소한 기능에 시간을 많이 쓰고 싶지 않았다
)
구글링한 결과 react-full-page
라는 패키지를 손쉽게 찾을 수 있었다. 이 기능을 full-page 라고 부르는 것도 패키지를 검색하다가 알게 되었다. MIT 라이센스라 무료로 사용가능했고 컴포넌트만 불러와서 사용하면 될 정도로 너무 간편했다.
https://github.com/zwug/react-full-page#readme
[GitHub - zwug/react-full-page: Full screen scrolling with React
Full screen scrolling with React. Contribute to zwug/react-full-page development by creating an account on GitHub.
github.com](https://github.com/zwug/react-full-page#readme)
1-1. react-full-page 사용법
사용법은 간단하다. FullPage라는 컴포넌트로 슬라이드를 넣어주고 싶은 컴포넌트를 감싸주면 된다. 이후 하나하나의 섹션을 'Slide'라는 컴포넌트로 감싸주면 끝이다..
추가적으로 FullPage 컴포넌트에 controls라는 props를 전달해주면, 컨트롤러 같은 ui가 자동으로 생성된다.
import React from 'react';
import { FullPage, Slide } from 'react-full-page';
export default class FullPageExample extends React.Component {
render() {
return (
<FullPage controls>
<Slide>
<h1>Inner slide content</h1>
</Slide>
<Slide>
<h1>Another slide content</h1>
</Slide>
</FullPage>
);
}
});
이렇게 간단하게 full-page 스크롤 기능을 완성했다.
하지만 뭔가 찜찜했다. 이렇게 간단한 기능인데 패키지를 설치하여 빌드시 20kb나 낭비를 해야하는 것이 마음에 걸렸다.
2. scroll snap
결국 패키지를 제거하고 구글링을 한 결과, CSS의 scroll snap이라는 속성을 있다는 걸 알게 되었다.
scroll snap은 웹페이지에서 수평 또는 수직으로 스크롤하는 경우 요소가 스크롤되는 방식을 제어하는 기능이다. 이 속성으로 사용자가 스크롤 할 때 특정 위치에서 요소를 자연스럽게 정지시키거나, 특정 지점을 정확하게 스크롤 할 수 있도록 도와준다.
scroll snap 속성은 4가지의 하위 속성을 지니고 있다.
- scroll-snap-type
- scroll-snap-align
- scroll-padding
- scroll-margin
2.1 scroll-snap-type
scroll-snap-type
속성은 스크롤이 동작하는 방식을 정한다.
즉, 축과 스냅 적용 방식 두 가지를 설정할 수 있다.
scroll-snap-type: x; /* 수평 축 */
scroll-snap-type: y; /* 수직 축 */
scroll-snap-type: block; /* block 축 */
scroll-snap-type: inline; /* inline 축 */
scroll-snap-type: both; /* 두 축을 개별적으로 스냅 */
/* Optional */
scroll-snap-type: x mandatory; /* 스크롤 위치와 스냅 위치가 일치할 때 스냅 */
scroll-snap-type: y proximity; /* 스크롤 위치가 스냅 위치에 가까워지면 스냅 */
mandatory 값은 스크롤을 할 때 요소가 항상 스냅된다. 즉, 사용자가 스크롤을 멈추면 요소가 항상 일정한 위치에 머무른다.
반면 proximity는 요소가 스냅될 때 스크롤의 진행 방향에 따라 요소의 위치가 결정된다. 사용자가 스크롤을 멈추면 요소는 가장 가까운 스냅 위치에 머무르게 된다.
2.2 scroll-snap-align
scroll-snap-align 속성은 스냅이 적용되는 요소가 스냅 위치에 정렬되는 방식을 지정한다.
scroll-snap-align: none;
scroll-snap-align: start; /* snap이 되는 지점을 시작부분으로 한다 */
scroll-snap-align: end; /* snap이 되는 지점을 끝으로 한다 */
scroll-snap-align: start end; /* 두개가 있다면 첫번째는 block 두번째는 inline을 의미한다 */
scroll-snap-align: center; /* snap이 되는 지점을 중간으로 한다 */
2.3 scroll-padding
scroll-padding 은 해당 요소에 진짜 padding을 넣는 것이 아니고, 해당 뷰포트의 padding이 적용된다.
scroll-snap-type:y mandatory;
scroll-padding: 50px;
2.4 snap-scroll-margin
padding과 비슷하게 뷰포트에 margin을 지정하는 속성이다.
.snap-area:nth-of-type(2) { scroll-margin: 100px; }
snap-scroll-padding, snap-scroll-margin은 유저에게 예상 시나리오를 보여주거나 이전 화면과 연속성을 주고싶을 때 사용하면 좋은 속성이라고 생각한다.
3. 적용
위의 내용을 바탕으로 styled-components 를 사용해서 간단하게 기능을 구현할 수 있었다.
import styled from 'styled-components';
function ScrollSnap() {
return (
<Container>
<Item>Item 1</Item>
<Item>Item 2</Item>
<Item>Item 3</Item>
<Item>Item 4</Item>
</Container>
)
}
const Container = styled.div`
width: 100%;
height: 100%;
overflow-y: scroll;
scroll-snap-type: y mandatory;
`;
const Item = styled.div`
width: 100vw;
height: 100vh;
scroll-snap-align: start;
`;
Container 컴포넌트는 스크롤 가능한 영역을 만들고, Item 컴포넌트는 스크롤될 요소를 나타낸다.