建立分頁元件

概略說明如何建構類似 iOS 和 Android 應用程式中的分頁元件。

在本文中,我想分享如何在網路上建構回應式分頁元件:支援多種裝置輸入,並支援多種瀏覽器。試試示範

示範

如果你偏好使用影片,也可以觀看這篇 YouTube 文章:

總覽

分頁是設計系統的常見元件,但可以採用許多形狀和形式。首先是電腦分頁以 <frame> 元素為基礎,而現在,我們加入了寬敞的行動裝置元件,可根據物理屬性建立動畫效果。他們都希望這麼做:節省空間。

目前分頁使用者體驗的基本要素是按鈕導覽區域,可讓使用者切換顯示頁框中的內容顯示設定。許多不同內容區域會共用相同的空間,但會根據導覽中所選按鈕的特定條件顯示。

由於元件概念具有網路多元化的風格,所以美術拼貼變得相當混亂
過去 10 年以來分頁元件網頁設計樣式的美術拼貼

網路戰術

總而言之,由於有幾項重要的網路平台功能,我發現這個元件在建構時都相當簡單:

  • scroll-snap-points:適用於優雅滑動,以及透過鍵盤與適當的捲動停止位置進行互動
  • 深層連結 (透過網址雜湊),由瀏覽器處理的網頁內捲動錨定及分享支援
  • 螢幕閱讀器支援與 <a>id="#hash" 元素標記
  • prefers-reduced-motion:可啟用交叉淡出轉場效果和網頁內捲動即時捲動功能
  • 草稿 @scroll-timeline 網路功能可動態底線及色彩變更所選分頁

HTML

基本上,這裡的使用者體驗為:按一下連結,讓網址代表巢狀頁面狀態,然後在瀏覽器捲動至相符元素時看到內容區域更新。

其中包含一些結構內容成員:連結和 :target。請提供連結清單 (<nav> 適合使用),以及 <article> 元素清單 (適用於 <section>)。每個連結雜湊都會與一個區段相符,讓瀏覽器可透過錨定標記捲動內容。

已按下連結按鈕,並滑動至焦點主題

舉例來說,按一下連結會自動聚焦在 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>

我可以使用 hrefid 屬性,在 <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>

接下來,我在文章中填入了兩件大英文字,連結包含混合長度和圖片集的圖片。只要內容可搭配內容,即可開始進行版面配置

捲動版面配置

這個元件有 3 種不同的捲動區域:

  • 導覽 (粉紅色) 可以水平捲動
  • 內容區域 (藍色) 可以水平捲動
  • 每個文章項目 (綠色) 都可以垂直捲動。
3 個彩色方塊 (有顏色對應方向箭頭),輪廓區域描繪捲動區域和捲動方向。

捲動作業涉及 2 種不同類型的元素:

  1. 視窗
    包含已定義尺寸且具有 overflow 屬性樣式的方塊。
  2. 超大型介面
    在這個版面配置中,是清單容器:導覽連結、章節文章和文章內容。

<snap-tabs>」版面配置

我選擇的頂層版面配置為 Flexbox。我將方向設為 column,這樣標頭和區段就會垂直排序。這是第一個捲動視窗,會隱藏所有溢位的隱藏視窗。標頭和區段很快就會以個別可用區的形式使用過度捲動。

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 元素上有熱粉色重疊,概述在元件中佔用的空間

接下來,捲動樣式結果顯示,我們可以共用 2 個水平捲動區域 (標頭和區段) 的捲動樣式,因此建立了公用程式類別 .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> 版面配置

導覽連結必須排成一行,且沒有換行符號、垂直置中,且每個連結項目應對齊捲動畫面容器。Swift 適用於 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;
  }
}

每種連結樣式和大小本身,因此導覽版面配置只需要指定方向和流程。導覽項目採用不重複的寬度,會導致分頁之間的轉換效果隨著指標調整至新目標寬度。瀏覽器是否會顯示捲軸,視這裡的元素數量而定。

導覽列的元素上有熱粉色重疊,概述這些元素在元件中佔用的空間和溢位位置

分頁 <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;
}

我選擇讓文章對齊上層捲軸。導覽連結項目和文章元素如何貼齊各捲動容器的內嵌啟動,我真的很滿意。看來這就像是和諧的關係

文章元素和其子元素上會有熱粉重疊重疊,概述其在元件中佔用的空間,以及溢位的方向

這篇文章是格狀子項,因此系統會預先決定大小,就是我們希望提供捲動 UX 的可視區域區域。這表示我不需要設定任何高度或寬度樣式,只需要定義溢位方式即可。我將 overflow-y 設為自動,接著使用便利的過度捲動行為屬性擷取捲動互動。

3 個捲動區域回顧

我在系統設定中選擇了「一律顯示捲軸」。我認為在開啟這項設定時,版面配置確實重要,因為我可以檢查版面配置和捲動自動化調度管理。

將 3 個捲軸設為顯示,現在會耗用版面配置空間,而且元件依然美觀

我認為在這個元件中看到捲軸空白邊,有助清楚顯示捲動區域的位置、支援的方向,以及彼此互動的方式。請考量每個捲動視窗頁框對版面配置的彈性或格狀父項。

開發人員工具可協助我們視覺化呈現以下內容:

捲動區域會有格線和 Flexbox 工具重疊,概述元件在元件中佔用的空間,以及溢位的方向
Chromium 開發人員工具,顯示滿是錨點元素的 Flexbox 導覽元素版面配置、滿是文章元素的格線區段版面配置,以及充滿段落和標題元素的文章元素。

捲動版面配置已完成:貼齊、深層連結,以及可透過鍵盤存取。奠定堅實基礎,提升使用者體驗、設計風格和討喜。

精選內容

捲動貼齊的子項在調整大小時會維持鎖定位置。這表示 JavaScript 不需要在裝置旋轉或瀏覽器大小時將任何內容帶入檢視畫面。歡迎在 Chromium 開發人員工具裝置模式中選取「回應式」以外的任何模式,然後調整裝置影格大小。請注意,元素會一直顯示在畫面中,並隨著內容鎖定。自 Chromium 根據規格更新實作後,已可使用這項功能。歡迎參閱以下這篇網誌文章

動畫

動畫的作用是明確連結與 UI 意見回饋的互動。這有助於引導或協助使用者輕鬆探索所有內容。我要依目的和條件新增動作使用者現在可以在作業系統中指定動作偏好設定,而且我完全喜歡在介面中回應他們的偏好設定。

我要連結定位點底線與文章捲動位置。貼齊除了貼齊功能外,還能錨定動畫的開頭和結尾。這會保留與內容連結的 <nav>,就像迷你地圖一樣。我們會從 CSS 和 JS 檢查使用者的動作偏好設定。有一些地方值得一探究竟!

捲動行為

您有機會可以強化 :targetelement.scrollIntoView() 的動作行為。預設為立即啟動。瀏覽器只會設定捲動位置。如果想要改用該捲動位置 而不是閃爍那一個位置呢?

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

由於我們在此介紹的是動態效果,以及使用者無法控制的動作 (例如捲動),因此只有在使用者沒有在作業系統中減少動作時,我們才會套用此樣式。這樣一來,我們就能只為合適的使用者提供捲動動作。

分頁指標

本動畫的目的是將指標與內容狀態建立關聯。我決定為偏好減少動作的使用者套用色彩交叉漸變的 border-bottom 樣式,以及讓喜歡動作的使用者透過捲動連結的滑動式 + 色彩淡出動畫。

在 Chromium 開發人員工具中,我可以切換偏好設定,並示範 2 種不同的轉換樣式。我有一堆有趣的建築設計了。

@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 是無作用。這是草稿規格,只包含實驗性實作項目。但有 polyfill。在這個示範中,我會用它

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
});

我想讓 1 個項目遵循彼此的捲動位置,並建立 ScrollTimeline,也就是定義捲動連結的驅動程式 scrollSource。一般而言,網路上的動畫會以全域時針刻度執行,但在記憶體中,使用自訂 sectionScrollTimeline 時,我可以變更所有設定。

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

進入動畫的主要畫面格前,需要先指出捲動的追蹤者 (tabindicator) 會根據自訂時間軸 (即區段的捲動) 產生動畫效果。這樣會完成連結,但缺少在建立動畫時所需的最終有狀態點 (也稱為主要畫面格)。

動態主要畫面格

使用 @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 強化功能

值得注意的是,我在這裡展示的核心概念無需 JavaScript 就能運作。談到這一點,我們來看看在 JS 適用時如何強化這項功能。

深層連結比較在行動裝置術語中,但我認為深層連結的用意在於利用分頁,您可以直接分享分頁內容的網址。瀏覽器會在網頁內導覽至與網址雜湊中相符的 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); });

每當使用者捲動區段時,請清除區段逾時,並啟動新的區段。區段停止捲動時,請勿清除逾時,休息後會觸發 100 毫秒。啟動時,呼叫會試圖找出使用者停止位置的函式。

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

  matchingNavItem && setActiveTab(matchingNavItem);
};

假設捲動畫面貼齊,請將目前捲動位置與捲動區域的寬度除以兩者,會產生整數,而不是小數。接著我會試著透過計算的索引,從快取中擷取一個導覽項目,如果有,就會傳送要設為啟用的相符項目。

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 則可透過宣告的方式管理使用者體驗。有時因為好玩又討喜

結論

現在你知道我怎麼做,你會怎麼做?這樣可以達到一些有趣的元件架構!誰將建構第 1 版,並在最喜歡的架構中使用運算單元?🙂

讓我們帶您更多元的方法,並瞭解運用網路打造網站的所有方式。 建立 GlitchTwitter 推文,我就會把版本添加到下方的「社群重混」部分。

社群重混作品