스토리 구성요소 빌드

웹에서 Instagram 스토리와 유사한 환경을 빌드하는 방법에 관한 기본 개요입니다.

이 게시물에서는 반응형이고 키보드 탐색을 지원하며 브라우저 전반에서 작동하는 웹용 스토리 구성요소를 빌드하는 방법에 관한 생각을 공유하고자 합니다.

데모

이 스토리 구성요소를 직접 빌드하는 실습 데모를 원한다면 스토리 구성요소 Codelab을 확인하세요.

동영상을 선호하는 경우 이 게시물의 YouTube 버전을 참고하세요.

개요

스토리 UX의 대표적인 예로는 Snapchat 스토리와 Instagram 스토리가 있습니다 (플릿은 말할 것도 없음). 일반적인 UX 측면에서 스토리는 일반적으로 여러 구독을 탐색하기 위한 모바일 전용 탭 중심 패턴입니다. 예를 들어 Instagram에서 사용자는 친구의 스토리를 열고 그 안에 있는 사진을 살펴봅니다. 일반적으로 한 번에 여러 친구를 추가합니다. 기기의 오른쪽을 탭하면 사용자는 해당 친구의 다음 스토리로 건너뜁니다. 오른쪽으로 스와이프하면 다른 친구로 건너뜁니다. 스토리 구성요소는 캐러셀과 매우 유사하지만 단일 차원 배열이 아닌 다차원 배열을 탐색할 수 있습니다. 각 캐러셀 내부에 캐러셀이 있는 것과 같습니다. 🤯

카드를 사용하여 다차원 배열을 시각화했습니다. 왼쪽에서 오른쪽으로 보라색 테두리 카드가 쌓여 있고 각 카드 안에는 청록색 테두리 카드가 1개 이상 있습니다. 목록 내 목록입니다.
친구의 첫 번째 캐러셀
스토리의 두 번째 '누적' 캐러셀
👍 목록 내 목록(다차원 배열)

작업에 적합한 도구 선택하기

전반적으로 이 구성요소는 몇 가지 중요한 웹 플랫폼 기능 덕분에 매우 간단하게 빌드할 수 있었습니다. 그럼 살펴보겠습니다.

CSS 그리드

CSS 그리드에는 콘텐츠를 처리하는 강력한 방법이 몇 가지 있으므로 이 레이아웃은 CSS 그리드에 어려운 작업이 아니었습니다.

친구 레이아웃

기본 .stories 구성요소 래퍼는 모바일 중심의 가로 스크롤뷰입니다.

.stories {
  inline-size: 100vw;
  block-size: 100vh;

  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;

  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  touch-action: pan-x;
}

/* desktop constraint */
@media (hover: hover) and (min-width: 480px) {
  max-inline-size: 480px;
  max-block-size: 848px;
}
Chrome DevTools의 기기 모드를 사용하여 그리드로 만든 열 강조표시

grid 레이아웃을 분석해 보겠습니다.

  • 모바일에서는 100vh 및 100vw로 표시 영역을 명시적으로 채우고 데스크톱에서는 크기를 제한합니다.
  • /는 행과 열 템플릿을 구분합니다.
  • auto-flow는 grid-auto-flow: column로 변환됩니다.
  • 자동 흐름 템플릿은 100%이며, 이 경우 스크롤 창 너비가 무엇이든 상관없습니다.

휴대전화에서는 행 크기를 표시 영역 높이로, 각 열을 표시 영역 너비로 생각하면 됩니다. Snapchat 스토리 및 Instagram 스토리 예시를 계속 살펴보면 각 열은 친구의 스토리가 됩니다. 친구 스토리가 표시 영역 외부로 계속 이어지도록 하여 스크롤할 수 있는 공간을 확보합니다. 그리드는 각 친구 스토리에 대해 HTML을 배치하는 데 필요한 만큼의 열을 만들어 동적이고 반응형인 스크롤 컨테이너를 만듭니다. Grid를 사용하면 전체 효과를 중앙 집중화할 수 있었습니다.

스태킹

각 친구에 대해 페이지로 나눌 수 있는 상태의 스토리가 필요합니다. 애니메이션과 기타 재미있는 패턴을 준비하기 위해 스택을 선택했습니다. 여기서 스택은 샌드위치를 옆에서 보는 것이 아니라 위에서 내려다보는 것을 의미합니다.

CSS 그리드를 사용하면 행과 열이 별칭([story])을 공유하는 단일 셀 그리드(즉, 정사각형)를 정의한 다음 각 하위 요소가 별칭이 지정된 단일 셀 공간에 할당됩니다.

.user {
  display: grid;
  grid: [story] 1fr / [story] 1fr;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
.story {
  grid-area: story;
  background-size: cover;
  …
}

이렇게 하면 HTML이 쌓기 순서를 제어하고 모든 요소를 흐름에 유지할 수 있습니다. absolute 위치 지정이나 z-index로 아무것도 할 필요가 없었고 height: 100% 또는 width: 100%로 상자를 수정할 필요도 없었습니다. 상위 그리드가 이미 스토리 사진 표시 영역의 크기를 정의했으므로 이러한 스토리 구성요소 중 어느 것도 채우도록 지시할 필요가 없었습니다.

CSS 스크롤 맞추기 지점

CSS 스크롤 스냅 포인트 사양을 사용하면 스크롤 시 표시 영역에 요소를 쉽게 고정할 수 있습니다. 이러한 CSS 속성이 존재하기 전에는 JavaScript를 사용해야 했으며, 이는 까다로운 작업이었습니다. Sarah Drasner의 CSS 스크롤 스냅 포인트 소개에서 사용 방법을 자세히 알아보세요.

scroll-snap-points 스타일이 없는 경우와 있는 경우의 가로 스크롤. 이 속성이 없으면 사용자는 평소와 같이 자유롭게 스크롤할 수 있습니다. 이 기능을 사용하면 브라우저가 각 항목에 부드럽게 안착합니다.
부모
.stories {
  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  touch-action: pan-x;
}
오버스크롤이 있는 상위 요소가 스냅 동작을 정의합니다.
하위 요소
.user {
  display: grid;
  grid: [story] 1fr / [story] 1fr;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
하위 요소는 맞추기 타겟으로 선택됩니다.

제가 스크롤 스냅 포인트를 선택한 이유는 다음과 같습니다.

  • 무료 접근성. 스크롤 스냅 포인트 사양에 따르면 왼쪽 화살표 및 오른쪽 화살표 키를 누르면 기본적으로 스냅 포인트를 통해 이동해야 합니다.
  • 성장하는 사양. 스크롤 스냅 포인트 사양은 항상 새로운 기능과 개선사항을 제공하므로 스토리 구성요소는 앞으로 더 개선될 것입니다.
  • 손쉬운 구현. 스크롤 맞춤 지점은 터치 중심의 가로 페이지로 나누기 사용 사례를 위해 실질적으로 빌드되었습니다.
  • 무료 플랫폼 스타일 관성. 모든 플랫폼은 이상한 스크롤 및 정지 스타일을 가질 수 있는 정규화된 관성과 달리 자체 스타일로 스크롤하고 정지합니다.

교차 브라우저 호환성

Opera, Firefox, Safari, Chrome, Android, iOS에서 테스트했습니다. 다음은 기능과 지원에서 차이점이 발견된 웹 기능에 대한 간략한 설명입니다.

하지만 일부 CSS가 적용되지 않아 일부 플랫폼에서 현재 UX 최적화가 누락되었습니다. 이러한 기능을 관리할 필요가 없다는 점이 마음에 들었고, 결국 다른 브라우저와 플랫폼에도 적용될 것이라고 확신합니다.

scroll-snap-stop

캐러셀은 CSS 스크롤 스냅 포인트 사양을 만들게 된 주요 UX 사용 사례 중 하나였습니다. 스토리와 달리 캐러셀은 사용자가 상호작용한 후 각 이미지에서 항상 중지할 필요는 없습니다. 캐러셀을 빠르게 순환하는 것이 좋거나 권장될 수 있습니다. 반면 스토리는 하나씩 탐색하는 것이 가장 좋으며, scroll-snap-stop가 바로 이 기능을 제공합니다.

.user {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

이 게시물을 작성하는 시점에는 scroll-snap-stop가 Chromium 기반 브라우저에서만 지원됩니다. 업데이트는 브라우저 호환성을 확인하세요. 하지만 차단 요소는 아닙니다. 지원되지 않는 브라우저에서는 사용자가 실수로 친구를 건너뛸 수 있다는 의미입니다. 따라서 사용자가 더 주의해야 하거나, 건너뛴 친구가 조회된 것으로 표시되지 않도록 JavaScript를 작성해야 합니다.

관심이 있다면 사양에서 자세히 알아보세요.

overscroll-behavior

모달을 스크롤하다가 갑자기 모달 뒤의 콘텐츠를 스크롤하기 시작한 적이 있나요? overscroll-behavior를 사용하면 개발자가 스크롤을 가두고 벗어나지 못하게 할 수 있습니다. 다양한 상황에 적합합니다. My Stories 구성요소는 추가 스와이프 및 스크롤 동작이 구성요소를 벗어나지 않도록 이 구성요소를 사용합니다.

.stories {
  overflow-x: auto;
  overscroll-behavior: contain;
}

Safari와 Opera는 이를 지원하지 않는 두 브라우저였으며, 이는 전혀 문제가 되지 않습니다. 이러한 사용자는 익숙한 오버스크롤 환경을 사용하게 되며 이 개선사항을 알아차리지 못할 수도 있습니다. 저는 이 효과를 매우 좋아하며, 구현하는 거의 모든 오버스크롤 기능에 포함합니다. 이는 UX 개선으로만 이어질 수 있는 무해한 추가 기능입니다.

scrollIntoView({behavior: 'smooth'})

사용자가 탭하거나 클릭하여 친구의 스토리 세트 끝에 도달하면 스크롤 맞춤 지점 세트의 다음 친구로 이동할 차례입니다. JavaScript를 사용하면 다음 친구를 참조하고 스크롤하여 표시하도록 요청할 수 있었습니다. 이에 대한 기본 지원은 훌륭합니다. 모든 브라우저에서 뷰로 스크롤했습니다. 하지만 모든 브라우저가 'smooth'를 지원하지는 않았습니다. 즉, 스냅되는 대신 스크롤되어 표시됩니다.

element.scrollIntoView({
  behavior: 'smooth'
})

Safari는 여기서 behavior: 'smooth'를 지원하지 않는 유일한 브라우저였습니다. 업데이트는 브라우저 호환성을 확인하세요.

실무

이제 제가 어떻게 했는지 알았으니 여러분은 어떻게 하시겠어요? 접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. 데모를 만들고 트윗으로 보내주시면 아래의 커뮤니티 리믹스 섹션에 추가하겠습니다.

커뮤니티 리믹스