페이지 넘김 형식으로 브라우징하기 - CSS Multi-column layout


개요

(이하 ChatGPT가 추천한 개요)

이 포스팅에서는 HTML 컨텐츠를 페이지 형식으로 배열하고, 스크롤 대신 페이지 넘김 형식으로 브라우징할 수 있는 방법을 구현하는 방법에 대해 다룹니다. 이를 위해 CSS Multi-column layout을 적용하고, 스크롤을 구현하는 데 JavaScript를 사용합니다. 또한, 화면 크기 변경 시 해당 항목의 페이지 위치를 찾아서 보여주는 기능도 구현합니다. 이러한 방법으로 React를 사용하여 다음과 같은 디자인을 구현합니다.

배경

대부분의 웹 사이트는 길쭉한 레이아웃으로 화면을 구성하고, 사용자가 스와이프해서 레이아웃을 둘러볼 것을 가정하는 게 보통이다. 예를 들면 iOS 디바이스에서 위에서 아래로 스와이프하면 화면을 새로고침하는데, 이는 사용자들이 화면을 아래쪽으로 스크롤할 것을 가정하고 만든 디자인이라고 말할 수 있다. 반대로 아래에서 위로 스와이프할 땐 스크롤만 하지 새로고침은 안하잖아?

근데 컨텐츠가 너무 길어서 스와이프를 계속 하다보면 태블릿 화면 한쪽이 손가락으로 문대져서 맨들맨들해지는 것을 볼 수 있다. 그리고 나이를 먹었는지 스크롤하는 화면을 눈으로 따라다니기가 싫어진다. 그냥 다음 페이지로 넘어가서 처음부터 읽는 게 독자 입장에선 맘편하다.

요구사항

전체 컨텐츠를 CSS Multi-column layout 으로 배열하고, 다음 페이지로 넘어가기 부분을 구현한다.

구현

CSS Multi-column layout 적용

CSS가 대충 다 해준다. 가로화면에선 column 두개, 세로화면에선 한개 하는 식도 가능하다.

스크롤 적용

CSS로 할 방법이 없어서 Javascript로 구현했다. 구조적으로 Javascript에서 직접 레이아웃 계산을 하는 게 아니기 때문에, CSS를 통해 계산된 레이아웃을 가져와서 스크롤에 필요한 부분을 계산해야 했다. 간단히 설명하자면,

현재 페이지에 보이는 element들을 가지고 있다가, 다음 페이지에 속하는 element의 getBoundingClientRect(), 이전 페이지에 해당하는 getBoundingClientRect()를 한땀한땀 가져와서 미리 저장해 둔다.

다음/이전 페이지로 이동하라는 명령이 떨어지면, 미리 계산해둔 다음 페이지 위치로 scroll시킨다. (명령 시점에 계산하지 않는 이유는, 계산하다 시간 오래걸려서 반응성 떨어질까봐 그러는 건데, 사실 한 화면에 100개 미만 항목을 표시한다는 가정 하에선 이런 최적화가 별로 의미없어 보이기도 하다)

화면 크기 변경 적용

현재 보이고 있는 항목을 cursor처럼 가지고 있다가, 화면 크기가 바뀐 시점에 다시 해당 항목의 페이지 위치를 찾아서 보여주면 된다.

디자인

이런 식으로 구현해놓고 나자 React 항목은 다음과 같은 디자인이 되었다.

  • props
    • children: 레이아웃에 들어갈 항목들.
  • useState
    • cursor: 현재 보여지고 있는 항목을 가리키는 포인터. 실제론 children 배열의 인덱스 값이다.
    • nav: 다음/이전 페이지에 해당하는 항목들을 가리키는 포인터 모음. 즉, 인덱스 값 두개.
  • useEffect(..., [cursor])
    • 변경된 cursor 값에 따라 페이지 변환을 해야 한다
    • 현재 cursor에 맞춰 다음/이전 페이지 계산을 수행한다.
    • 창 크기가 변경된 경우, cursor값이 바뀌지 않았어도 다음/이전 페이지 계산을 다시 수행해야 한다. 해당 이벤트 처리기를 여기서 등록한다.
  • useEffect(..., [cursor, nav])
    • 다음/이전 페이지 요청이 들어왔을 때, nav 값에 따라 페이지 이동을 수행해야 한다.

데모는?

블로그에 삽입해야 하는 시점에서 데모의 효용가치가 별로 없다;; 외부 도메인에 데모를 떨렁 두는 것도 별로 좋은 것 같지 않고… 하여튼 요건 데모 없습니다.

문제점

Javascript로 직접 구현한 스크롤 부분이 영 말썽이다. 내용물 element가 padding을 가지고 있는 경우, getBoundingClientRect()에선 해당 padding을 제외한 걸 기준으로 박스를 반환해서 종종 원하는 위치와 다른 곳으로 스크롤이 되어있을 수 있다. 고칠 수 있는데… 개인 프로젝트는 이런 소소한 부분의 완성도가 중요하지 않아서 버려지게 된다. 일단 내가 필요한 게 되니까 OK!