Tabs 구성요소 빌드

iOS 및 Android 앱에 있는 것과 유사한 탭 구성요소를 빌드하는 방법에 관한 기본 개요입니다.

이 게시물에서는 반응형이고 여러 기기 입력을 지원하며 브라우저 전반에서 작동하는 웹용 탭 구성요소를 빌드하는 방법에 관한 생각을 공유하고자 합니다. 데모를 사용해 보세요.

데모

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

개요

탭은 디자인 시스템의 일반적인 구성요소이지만 다양한 모양과 형태를 취할 수 있습니다. 처음에는 <frame> 요소를 기반으로 빌드된 데스크톱 탭이 있었고, 이제는 물리적 속성을 기반으로 콘텐츠에 애니메이션을 적용하는 부드러운 모바일 구성요소가 있습니다. 모두 공간을 절약한다는 동일한 목적을 가지고 있습니다.

오늘날 탭 사용자 환경의 핵심은 디스플레이 프레임에서 콘텐츠의 공개 상태를 전환하는 버튼 탐색 영역입니다. 다양한 콘텐츠 영역이 동일한 공간을 공유하지만 탐색에서 선택한 버튼에 따라 조건부로 표시됩니다.

웹에서 구성요소 개념에 적용한 스타일이 매우 다양하기 때문에 콜라주가 상당히 혼란스럽습니다.
지난 10년간의 탭 구성요소 웹 디자인 스타일 콜라주

웹 전술

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

  • 적절한 스크롤 중지 위치를 사용하여 세련된 스와이프 및 키보드 상호작용을 위한 scroll-snap-points
  • 브라우저에서 처리하는 페이지 내 스크롤 고정 및 공유 지원을 위한 URL 해시를 통한 딥 링크
  • <a> 및 id="#hash" 요소 마크업을 사용한 스크린 리더 지원
  • prefers-reduced-motion: 크로스페이드 전환 및 즉각적인 인페이지 스크롤 사용 설정
  • 선택한 탭에 동적으로 밑줄을 긋고 색상을 변경하는 초안 상태의 @scroll-timeline 웹 기능

HTML

기본적으로 여기서 UX는 링크를 클릭하고, URL이 중첩된 페이지 상태를 나타내도록 하고, 브라우저가 일치하는 요소로 스크롤할 때 콘텐츠 영역이 업데이트되는 것을 확인하는 것입니다.

링크와 :target 같은 구조적 콘텐츠 요소가 있습니다. <nav>에 적합한 링크 목록과 <section>에 적합한 <article> 요소 목록이 필요합니다. 각 링크 해시는 섹션과 일치하므로 브라우저가 앵커를 통해 스크롤할 수 있습니다.

링크 버튼을 클릭하면 포커스가 지정된 콘텐츠가 슬라이드됩니다.

예를 들어 링크를 클릭하면 Chrome 89에서 :target 도움말에 자동으로 포커스가 맞춰지며 JS가 필요하지 않습니다. 그러면 사용자는 평소와 같이 입력 장치로 기사 콘텐츠를 스크롤할 수 있습니다. 마크업에 표시된 대로 보완 콘텐츠입니다.

다음 마크업을 사용하여 탭을 구성했습니다.

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

다음과 같이 href 및 id 속성을 사용하여 <a> 요소와 <article> 요소 간의 연결을 설정할 수 있습니다.

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

다음으로 기사를 다양한 양의 lorem으로 채우고 링크를 다양한 길이와 이미지 세트의 제목으로 채웠습니다. 작업할 콘텐츠가 있으면 레이아웃을 시작할 수 있습니다.

스크롤 레이아웃

이 구성요소에는 세 가지 유형의 스크롤 영역이 있습니다.

  • 탐색(분홍색)은 가로로 스크롤할 수 있습니다.
  • 콘텐츠 영역(파란색)은 가로로 스크롤할 수 있습니다.
  • 각 기사 항목(녹색)은 세로로 스크롤할 수 있습니다.
스크롤 영역을 표시하고 스크롤 방향을 보여주는 색상 일치 방향 화살표가 있는 다채로운 상자 3개

스크롤과 관련된 요소에는 두 가지 유형이 있습니다.

  1. 창
    overflow 속성 스타일이 있는 정의된 크기의 상자입니다.
  2. 크기가 큰 표시 경로
    이 레이아웃에서는 탐색 링크, 섹션 기사, 기사 콘텐츠와 같은 목록 컨테이너입니다.

<snap-tabs> 레이아웃

제가 선택한 최상위 레이아웃은 flex (Flexbox)였습니다. 방향을 column로 설정했으므로 헤더와 섹션이 세로로 정렬됩니다. 이것은 첫 번째 스크롤 창이며, overflow hidden으로 모든 것을 숨깁니다. 헤더와 섹션은 곧 개별 영역으로 오버스크롤을 사용합니다.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

다채로운 3단계 스크롤 다이어그램을 다시 가리킵니다.

  • <header>는 이제 (분홍색) 스크롤 컨테이너가 될 준비가 되었습니다.
  • <section>은 (파란색) 스크롤 컨테이너가 될 준비가 되었습니다.

아래에서 VisBug로 강조 표시한 프레임을 통해 스크롤 컨테이너가 만든 창을 확인할 수 있습니다.

헤더 및 섹션 요소에 핫핑크 오버레이가 있어 구성요소에서 차지하는 공간을 표시합니다.

탭 <header> 레이아웃

다음 레이아웃도 거의 동일합니다. flex를 사용하여 세로 순서를 만듭니다.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator는 링크 그룹과 함께 가로로 이동해야 하며 이 헤더 레이아웃은 해당 단계를 설정하는 데 도움이 됩니다. 절대 위치로 배치된 요소가 없습니다.

nav 및 span.indicator 요소에 핫핑크 오버레이가 있어 구성요소에서 차지하는 공간을 표시합니다.

다음은 스크롤 스타일입니다. 두 가로 스크롤 영역 (헤더 및 섹션) 간에 스크롤 스타일을 공유할 수 있으므로 유틸리티 클래스 .scroll-snap-x를 만들었습니다.

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

각각 x축의 오버플로, 오버스크롤을 트랩하는 스크롤 포함, 터치 기기의 숨겨진 스크롤바, 마지막으로 콘텐츠 표시 영역을 잠그는 스크롤 스냅이 필요합니다. 키보드 탭 순서는 액세스 가능하며 모든 상호작용은 자연스럽게 포커스를 안내합니다. 스크롤 스냅 컨테이너는 키보드에서 멋진 캐러셀 스타일 상호작용도 제공합니다.

탭 헤더 <nav> 레이아웃

탐색 링크는 줄바꿈 없이 한 줄로 배치되어야 하며, 세로로 중앙에 배치되어야 하고, 각 링크 항목은 스크롤 스냅핑 컨테이너에 맞춰야 합니다. 2021년 CSS를 위해 신속하게 작업해 주셔서 감사합니다.

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

각 링크는 자체적으로 스타일과 크기를 지정하므로 탐색 레이아웃은 방향과 흐름만 지정하면 됩니다. 탐색 항목의 고유한 너비는 표시기가 새 타겟에 맞게 너비를 조정하므로 탭 간 전환을 재미있게 만듭니다. 여기에 있는 요소 수에 따라 브라우저에서 스크롤바를 렌더링할지 여부가 결정됩니다.

nav의 a 요소에는 핫핑크 오버레이가 있어 구성요소에서 차지하는 공간과 오버플로되는 위치를 보여줍니다.

탭 <section> 레이아웃

이 섹션은 가변 항목이며 공간을 가장 많이 차지해야 합니다. 또한 기사를 배치할 열을 만들어야 합니다. 다시 한번 CSS 2021을 위해 신속하게 작업해 주셔서 감사합니다! block-size: 100%는 이 요소를 확장하여 상위 요소를 최대한 채운 다음 자체 레이아웃의 경우 상위 요소 너비의 100%인 일련의 열을 만듭니다. 상위 요소에 강력한 제약 조건을 작성했으므로 여기서는 백분율이 잘 작동합니다.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

이는 "수직으로 최대한 확장하되, 강제적으로 확장"하라는 의미입니다(flex-shrink: 0로 설정한 헤더를 기억하세요. 이는 이러한 확장 강제에 대한 방어책입니다). 이로 인해 전체 높이 열 집합의 행 높이가 설정됩니다. auto-flow 스타일은 그리드가 항상 하위 요소를 가로줄로 배치하고 줄바꿈을 하지 않도록 지시합니다. 이는 상위 창을 오버플로하는 데 필요한 것입니다.

기사 요소에 핫핑크 오버레이가 있어 구성요소에서 차지하는 공간과 오버플로되는 위치를 표시합니다.

이해하기 어려운 경우가 있습니다. 이 섹션 요소는 상자에 맞지만 상자 집합도 만들었습니다. 시각 자료와 설명이 도움이 되기를 바랍니다

탭 <article> 레이아웃

사용자는 기사 콘텐츠를 스크롤할 수 있어야 하며 스크롤바는 오버플로가 있는 경우에만 표시되어야 합니다. 이러한 도움말 요소는 깔끔한 위치에 있습니다. 스크롤 상위 요소이자 스크롤 하위 요소입니다. 브라우저는 여기에서 까다로운 터치, 마우스, 키보드 상호작용을 처리합니다.

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

상위 스크롤러 내에서 기사가 스냅되도록 선택했습니다. 탐색 링크 항목과 도움말 요소가 각각의 스크롤 컨테이너의 인라인 시작 부분에 맞춰지는 방식이 정말 마음에 듭니다. 조화로운 관계처럼 보이고 느껴집니다.

article 요소와 하위 요소에 핫핑크 오버레이가 있어 구성요소에서 차지하는 공간과 오버플로 방향을 보여줍니다.

기사는 그리드 하위 요소이며 크기는 스크롤 UX를 제공하려는 표시 영역으로 미리 결정됩니다. 즉, 여기에는 높이나 너비 스타일이 필요하지 않고 오버플로 방식을 정의하기만 하면 됩니다. overflow-y를 auto로 설정한 다음, 편리한 overscroll-behavior 속성을 사용하여 스크롤 상호작용을 트랩합니다.

스크롤 영역 3개 요약

아래에서는 시스템 설정에서 '항상 스크롤바 표시'를 선택했습니다. 이 설정이 사용 설정된 상태에서 레이아웃이 작동하는 것이 레이아웃과 스크롤 조정을 검토하는 것만큼 중요하다고 생각합니다.

3개의 스크롤바가 표시되도록 설정되어 이제 레이아웃 공간을 차지하고 있으며 구성요소는 여전히 보기 좋습니다.

이 구성요소에서 스크롤바 여백을 보면 스크롤 영역이 어디에 있는지, 지원하는 방향은 무엇인지, 서로 어떻게 상호작용하는지 명확하게 알 수 있습니다. 이러한 각 스크롤 창 프레임이 레이아웃의 flex 또는 grid 상위 요소이기도 한 방식을 고려하세요.

DevTools를 사용하면 이를 시각화할 수 있습니다.

스크롤 영역에는 그리드 및 Flexbox 도구 오버레이가 있어 구성요소에서 차지하는 공간과 오버플로되는 방향을 보여줍니다.
Chromium Devtools, showing the flexbox nav element layout full of anchor elements, the grid section layout full of article elements, and the article elements full of paragraphs and a heading element.

스크롤 레이아웃이 완성되었습니다. 스냅, 딥 링크, 키보드 액세스가 가능합니다. UX 개선, 스타일, 만족도를 위한 견고한 기반입니다.

주요 기능 소개

스크롤 스냅된 하위 요소는 크기 조절 중에 고정된 위치를 유지합니다. 즉, 기기 회전 또는 브라우저 크기 조절 시 JavaScript가 표시 영역에 아무것도 가져올 필요가 없습니다. Chromium DevTools 기기 모드에서 반응형 이외의 모드를 선택한 다음 기기 프레임의 크기를 조절하여 사용해 보세요. 요소가 계속 표시되고 콘텐츠와 함께 고정됩니다. 이는 Chromium이 사양에 맞게 구현을 업데이트한 이후로 사용할 수 있습니다. 이에 관한 블로그 게시물을 참고하세요.

애니메이션

여기에서 애니메이션 작업의 목표는 상호작용을 UI 피드백과 명확하게 연결하는 것입니다. 이렇게 하면 사용자가 모든 콘텐츠를 원활하게 탐색할 수 있도록 안내하거나 지원할 수 있습니다. 목적에 따라 조건부로 모션을 추가할 예정입니다. 이제 사용자는 운영체제에서 모션 환경설정을 지정할 수 있으며, 저는 인터페이스에서 사용자의 환경설정에 응답하는 것을 매우 즐깁니다.

탭 밑줄을 기사 스크롤 위치와 연결하겠습니다. 스냅은 정렬을 보기 좋게 만들 뿐만 아니라 애니메이션의 시작과 끝을 고정하기도 합니다. 이렇게 하면 미니맵처럼 작동하는 <nav>가 콘텐츠에 연결된 상태로 유지됩니다. CSS와 JS 모두에서 사용자의 모션 환경설정을 확인합니다. 몇 가지 고려해야 할 사항이 있습니다.

스크롤 동작

:target 및 element.scrollIntoView()의 모션 동작을 개선할 수 있습니다. 기본적으로 즉시 적용됩니다. 브라우저는 스크롤 위치만 설정합니다. 그렇다면 깜박이는 대신 스크롤 위치로 전환하려면 어떻게 해야 할까요?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

여기에서는 모션을 도입하고 있으며 사용자가 제어하지 않는 모션(예: 스크롤)이므로 사용자가 운영체제에서 모션 감소를 선호하지 않는 경우에만 이 스타일을 적용합니다. 이렇게 하면 스크롤 모션을 원하는 사용자에게만 제공할 수 있습니다

탭 표시기

이 애니메이션의 목적은 표시기를 콘텐츠 상태와 연결하는 것입니다. 움직임이 줄어든 것을 선호하는 사용자를 위해 border-bottom 스타일을 크로스페이드하고 움직임이 괜찮은 사용자를 위해 스크롤 연결 슬라이딩 + 색상 페이드 애니메이션을 적용하기로 했습니다.

Chromium Devtools에서 환경설정을 전환하고 두 가지 전환 스타일을 보여줄 수 있습니다. 이 앱을 만드는 과정은 정말 즐거웠습니다.

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

사용자가 모션 감소를 선호하는 경우 더 이상 필요하지 않으므로 .snap-indicator를 숨깁니다. 그런 다음 border-block-end 스타일과 transition로 바꿉니다. 또한 탭 상호작용에서 활성 탐색 항목에 브랜드 밑줄 강조표시가 있을 뿐만 아니라 텍스트 색상도 더 어둡습니다. 활성 요소는 텍스트 색상 대비가 더 높고 밝은 언더라이트 강조 표시가 있습니다.

몇 줄의 CSS만 추가하면 사용자가 자신의 움직임 환경설정을 세심하게 존중받고 있다고 느낄 수 있습니다. 정말 마음에 들어요

@scroll-timeline

위 섹션에서는 모션 감소 크로스페이드 스타일을 처리하는 방법을 보여드렸으며, 이 섹션에서는 표시기와 스크롤 영역을 함께 연결하는 방법을 보여드리겠습니다. 다음은 재미있는 실험적 기능입니다. 여러분도 저만큼 기대하고 계시길 바랍니다

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

먼저 JavaScript에서 사용자의 모션 환경설정을 확인합니다. 이 결과가 false이면 사용자가 모션 감소를 선호한다는 의미이므로 스크롤 연결 모션 효과를 실행하지 않습니다.

if (motionOK) {
  // motion based animation code
}

이 글을 작성하는 시점에는 @scroll-timeline에 대한 브라우저 지원이 없습니다. 실험용 구현만 있는 초안 사양입니다. 하지만 이 데모에서 사용하는 폴리필이 있습니다.

ScrollTimeline

CSS와 JavaScript 모두 스크롤 타임라인을 만들 수 있지만, 애니메이션에서 실시간 요소 측정을 사용할 수 있도록 JavaScript를 선택했습니다.

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

한 항목이 다른 항목의 스크롤 위치를 따르도록 하고 싶고, ScrollTimeline를 만들어 스크롤 링크의 드라이버인 scrollSource를 정의합니다. 일반적으로 웹의 애니메이션은 전역 시간 프레임 틱에 따라 실행되지만 메모리에 커스텀 sectionScrollTimeline가 있으면 이 모든 것을 변경할 수 있습니다.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

애니메이션의 키프레임을 살펴보기 전에 스크롤의 팔로어인 tabindicator가 맞춤 타임라인, 즉 섹션의 스크롤에 따라 애니메이션 처리된다는 점을 지적하는 것이 중요합니다. 이렇게 하면 연결이 완료되지만, 애니메이션을 적용할 스테이트풀(Stateful) 지점(키프레임이라고도 함)이라는 최종 요소가 누락됩니다.

동적 키프레임

@scroll-timeline를 사용하여 애니메이션을 적용하는 매우 강력한 순수 선언적 CSS 방식이 있지만, 제가 선택한 애니메이션은 너무 동적이었습니다. auto 너비 간에 전환할 방법이 없으며 하위 요소 길이에 따라 여러 키프레임을 동적으로 만들 방법이 없습니다.

하지만 JavaScript는 이 정보를 가져오는 방법을 알고 있으므로 직접 하위 요소를 반복하고 런타임에 계산된 값을 가져옵니다.

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

각 tabnavitem에 대해 offsetLeft 위치를 구조화하고 이를 translateX 값으로 사용하는 문자열을 반환합니다. 이렇게 하면 애니메이션에 4개의 변환 키프레임이 생성됩니다. 너비도 마찬가지로 각각 동적 너비가 무엇인지 묻고 키프레임 값으로 사용됩니다.

다음은 내 글꼴 및 브라우저 환경설정을 기반으로 한 출력 예시입니다.

TranslateX 키프레임:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

너비 키프레임:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

전략을 요약하자면, 이제 탭 표시기는 섹션 스크롤러의 스크롤 스냅핑 위치에 따라 4개의 키프레임에 걸쳐 애니메이션을 적용합니다. 스냅 포인트는 키프레임 간의 명확한 구분을 만들고 애니메이션의 동기화된 느낌을 더해줍니다.

활성 탭과 비활성 탭이 VisBug 오버레이와 함께 표시되며, 두 탭 모두 대비 점수가 통과 수준임을 보여줍니다.

사용자는 상호작용을 통해 애니메이션을 구동하고, 표시기의 너비와 위치가 한 섹션에서 다음 섹션으로 변경되는 것을 보며, 스크롤을 완벽하게 추적합니다.

눈치채지 못하셨을 수도 있지만, 강조 표시된 탐색 항목이 선택될 때 색상이 전환되는 것이 매우 자랑스럽습니다.

강조표시된 항목의 대비가 더 높으면 선택되지 않은 밝은 회색이 더 뒤로 밀려 보입니다. 마우스를 가져가거나 선택할 때와 같이 텍스트의 색상을 전환하는 것은 일반적이지만, 스크롤 시 밑줄 표시기와 동기화하여 색상을 전환하는 것은 한 단계 더 나아간 것입니다.

방법은 다음과 같습니다.

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

각 탭 탐색 링크에는 밑줄 표시기와 동일한 스크롤 타임라인을 추적하는 이 새로운 색상 애니메이션이 필요합니다. 이전과 동일한 타임라인을 사용합니다. 스크롤 시 틱을 내보내는 역할이므로 원하는 모든 유형의 애니메이션에서 해당 틱을 사용할 수 있습니다. 이전과 마찬가지로 루프에 키프레임 4개를 만들고 색상을 반환합니다.

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

색상이 var(--text-active-color)인 키프레임은 링크를 강조표시하고, 그렇지 않으면 표준 텍스트 색상입니다. 중첩 루프를 사용하면 비교적 간단합니다. 외부 루프는 각 탐색 항목이고 내부 루프는 각 탐색 항목의 개인 키프레임입니다. 외부 루프 요소가 내부 루프 요소와 동일한지 확인하고 이를 사용하여 선택 시점을 파악합니다.

이 글을 쓰면서 정말 즐거웠습니다. 최고죠.

더 많은 자바스크립트 개선사항

여기에서 보여드리는 핵심은 JavaScript 없이 작동한다는 점을 기억하세요. 이제 JS를 사용할 수 있을 때 이를 개선하는 방법을 살펴보겠습니다.

딥 링크는 모바일 용어에 가깝지만, 탭의 콘텐츠로 직접 URL을 공유할 수 있다는 점에서 탭을 사용하면 딥 링크의 의도를 충족할 수 있다고 생각합니다. 브라우저는 URL 해시에서 일치하는 ID로 페이지 내에서 이동합니다. 이 onload 핸들러가 플랫폼 전반에 영향을 미친다는 것을 알게 되었습니다.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

스크롤 종료 동기화

사용자가 항상 클릭하거나 키보드를 사용하는 것은 아니며, 때로는 자유롭게 스크롤할 수 있어야 합니다. 섹션 스크롤러가 스크롤을 중지하면 착지하는 위치가 상단 탐색 메뉴와 일치해야 합니다.

스크롤 종료를 기다리는 방법은 다음과 같습니다. js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

섹션이 스크롤될 때마다 섹션 제한 시간이 있으면 이를 지우고 새 제한 시간을 시작합니다. 섹션 스크롤이 중지되면 제한 시간을 지우지 않고 휴식 후 100ms 후에 실행합니다. 실행되면 사용자가 중지한 위치를 파악하는 함수를 호출합니다.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

스크롤이 스냅되었다고 가정하면 현재 스크롤 위치를 스크롤 영역의 너비로 나누면 소수가 아닌 정수가 됩니다. 그런 다음 이 계산된 색인을 통해 캐시에서 navitem을 가져오려고 시도하고, 일치하는 항목이 있으면 일치하는 항목을 활성 상태로 설정하도록 전송합니다.

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

활성 탭을 설정하려면 먼저 현재 활성 탭을 지운 다음 수신 탐색 항목에 활성 상태 속성을 부여합니다. scrollIntoView() 호출에는 CSS와의 흥미로운 상호작용이 있으므로 주목할 필요가 있습니다.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

가로 스크롤 스냅 유틸리티 CSS에서 사용자가 모션에 민감하지 않은 경우 smooth 스크롤을 적용하는 미디어 쿼리를 중첩했습니다. JavaScript는 요소를 뷰로 스크롤하는 호출을 자유롭게 할 수 있으며 CSS는 UX를 선언적으로 관리할 수 있습니다. 때로는 아주 멋진 조합이 되기도 합니다.

결론

이제 제가 어떻게 했는지 알았으니 여러분은 어떻게 하시겠어요? 이렇게 하면 재미있는 구성요소 아키텍처가 만들어집니다. 누가 가장 먼저 자신이 좋아하는 프레임워크에서 슬롯을 사용해 첫 번째 버전을 만들까요? 🙂

접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. Glitch를 만들고, 여러분의 버전을 트윗해 주시면 아래의 커뮤니티 리믹스 섹션에 추가하겠습니다.

커뮤니티 리믹스