建立設定元件

說明如何建立包含滑桿和核取方塊的設定元件。

在本篇文章中,我想分享有關為網路建構設定元件的想法,這個元件可回應、支援多種裝置輸入內容,並在多個瀏覽器中運作。試用示範模式

示範

如果您偏好觀看影片,或想預覽我們正在建構的 UI/UX,請觀看 YouTube 上的簡短操作說明:

總覽

我將這個元件的各個面向分為以下幾個部分:

  1. 版面配置
  2. 顏色
  3. 自訂範圍輸入內容
  4. 自訂核取方塊輸入項
  5. 無障礙考量事項
  6. JavaScript

版面配置

這是第一個以 CSS 格線為主題的 GUI 挑戰示範!以下是使用 Chrome 開發人員工具網格醒目顯示的每個格線:

彩色輪廓和間距疊加,可顯示組成設定版面配置的所有方塊

僅限差距

最常用的版面配置:

foo {
  display: grid;
  gap: var(--something);
}

這個版面配置只會使用格線來填補區塊之間的缺口,因此稱之為「只是缺口」。

五種版面配置都採用這項策略,以下是全部顯示方式:

以邊框和填滿的空白區域醒目顯示的垂直格線版面配置

fieldset 元素包含每個輸入群組 (.fieldset-item),並使用 gap: 1px 在元素之間建立細線邊框。無需使用複雜的邊框解決方案!

已填補的空白
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
邊框秘訣
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

自然格線包覆

最複雜的版面配置是巨集版面配置,也就是 <main><form> 之間的邏輯版面配置系統。

將包裝內容置中

Flexbox 和 GridLayout 都提供 align-itemsalign-content 的功能,在處理包裝元素時,content 版面配置對齊會將空間分配給子項群組。

main {
  display: grid;
  gap: var(--space-xl);
  place-content: center;
}

主要元素使用 place-content: center 對齊速記符,因此在單欄和雙欄版面配置中,子項會以垂直和水平方式置中。

請觀看上方影片,瞭解包裝是否已發生,但「內容」會如何維持置中。

重複自動調整 minmax

<form> 會為每個區段使用自適應格線版面配置。這個版面配置會根據可用空間,從一列切換為兩列。

form {
  display: grid;
  gap: var(--space-xl) var(--space-xxl);
  grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch));
  align-items: flex-start;
  max-width: 89vw;
}

這個格線的 row-gap (--space-xl) 與 column-gap (--space-xxl) 有不同的值,可在回應式版面配置中加入自訂觸控功能。當欄堆疊時,我們希望有較大的間距,但不像在寬螢幕上那麼大。

grid-template-columns 屬性使用 3 個 CSS 函式:repeat()minmax()min()Una Kravets 曾撰寫一篇精彩的版面配置網誌文章,將其稱為 RAM

與 Una 相比,我們的版面配置有 3 個特別新增項目:

  • 我們會傳遞額外的 min() 函式。
  • 我們指定 align-items: flex-start
  • max-width: 89vw 樣式。

Evan Minto 在其部落格文章「Intrinsically Responsive CSS Grid with minmax() and min()」中,詳細說明了額外的 min() 函式。建議您閱讀該文章。flex-start 對齊修正功能可移除預設的拉伸效果,因此這個版面配置的子項不必具有相同的高度,可以是自然的內建高度。YouTube 影片提供這項對齊功能的簡要說明。

max-width: 89vw 值得在本篇文章中稍微說明。讓我展示有和沒有套用樣式的版面配置:

為什麼會這樣?指定 max-width 時,系統會為 auto-fit 版面配置演算法提供內容、明確的大小或明確定位大小,以便瞭解可在空間中放入多少個重複項目。雖然空間很明顯是「全寬」,但根據 CSS 格線規格,您必須提供明確的大小或最大大小。我提供了大小上限。

那麼,為什麼要使用 89vw?因為「它」適用於我的版面配置。我和其他幾位 Chrome 團隊成員正在調查,為何 100vw 這類較合理的值不足以解決問題,以及這是否確實是錯誤。

間距

這項版面配置的和諧感,主要來自有限的色版間距,具體來說是 7 個。

:root {
  --space-xxs: .25rem;
  --space-xs:  .5rem;
  --space-sm:  1rem;
  --space-md:  1.5rem;
  --space-lg:  2rem;
  --space-xl:  3rem;
  --space-xxl: 6rem;
}

只要使用格線、CSS @nest@media 第 5 層語法,即可有效運用這些流程。以下是範例,完整的 <main> 版面配置樣式集。

main {
  display: grid;
  gap: var(--space-xl);
  place-content: center;
  padding: var(--space-sm);

  @media (width >= 540px) {
    & {
      padding: var(--space-lg);
    }
  }

  @media (width >= 800px) {
    & {
      padding: var(--space-xl);
    }
  }
}

以居中內容為主的網格,預設會保留適當的邊框空間 (例如行動裝置)。但隨著可視區域變多,邊框間距會擴大向外延伸。 2021 CSS 看起來不錯!

還記得先前的「just for gap」版面配置嗎?以下是這些元素在這個元件中完整版的外觀:

header {
  display: grid;
  gap: var(--space-xxs);
}

section {
  display: grid;
  gap: var(--space-md);
}

顏色

這項設計的色彩運用恰到好處,既能表達出豐富的視覺效果,又能維持簡約風格。我的編輯方式如下:

:root {
  --surface1: lch(10 0 0);
  --surface2: lch(15 0 0);
  --surface3: lch(20 0 0);
  --surface4: lch(25 0 0);

  --text1: lch(95 0 0);
  --text2: lch(75 0 0);
}

我使用數字來為表面和文字顏色命名,而不是 surface-darksurface-darker 這類名稱,因為在媒體查詢中,我會翻轉這些顏色,而淺色和深色皆不具意義。

我會在偏好設定媒體查詢中翻轉這些值,如下所示:

:root {
  ...

  @media (prefers-color-scheme: light) {
    & {
      --surface1: lch(90 0 0);
      --surface2: lch(100 0 0);
      --surface3: lch(98 0 0);
      --surface4: lch(85 0 0);

      --text1: lch(20 0 0);
      --text2: lch(40 0 0);
    }
  }
}

在深入探究色彩語法細節之前,必須先快速掌握整體情況和策略。不過,由於我講得太快,讓我稍微回顧一下。

LCH?

不必深入瞭解顏色理論,LCH 就是以人為本的語法,可滿足我們對顏色的感知,而非以數學方式 (例如 255) 測量顏色。這對人類來說是最大的優勢,因為人類可以更輕鬆地寫字,而其他人類也能因應這些調整而調整。

pod.link/csspodcast 網頁的螢幕截圖,顏色 2:感知單集節目已顯示
請收聽 CSS Podcast,瞭解感知色彩等內容

今天在這個示範中,我們將著重於語法和我用來切換亮色和深色值的值。我們來看看 1 個表面和 1 個文字顏色:

:root {
  --surface1: lch(10 0 0);
  --text1:    lch(95 0 0);

  @media (prefers-color-scheme: light) {
    & {
      --surface1: lch(90 0 0);
      --text1:    lch(40 0 0);
    }
  }
}

--surface1: lch(10 0 0) 會轉譯為 10% 亮度、0 色度和 0 色相:非常深的無色灰色。接著,在淺色模式的媒體查詢中,亮度會透過 --surface1: lch(90 0 0); 切換為 90%。這就是這項策略的重點。首先,請只變更 2 個主題之間的亮度,並維持設計所需的對比率,或可維持無障礙功能的對比率。

lch() 的額外優點是亮度是人類導向的,我們可以對 % 的變更感到滿意,因為它會在感知上和一致性上呈現 % 的差異。舉例來說,hsl() 並不可靠

如有興趣,請參閱這篇文章,進一步瞭解色彩空間和 lch()。即將登場!

CSS 目前完全無法存取這些顏色。我再重複一次:我們無法存取大多數現代顯示器的三分之一顏色。這些顏色不只是任何顏色,也是螢幕可顯示最鮮明的色彩。我們的網站會變得模糊,是因為監控硬體的演進速度,比 CSS 規格和瀏覽器實作更快。

Lea Verou

採用色彩配置的自動調整表單控制項

許多瀏覽器都會提供深色主題控制項 (目前為 Safari 和 Chromium),但您必須在設計使用這類控制項的 CSS 或 HTML 中指定。

上述範例是透過 DevTools 的「Styles」面板,展示屬性的效果。這個示範使用 HTML 標記,我認為這是比較好的選項:

<meta name="color-scheme" content="dark light">

如要進一步瞭解這項功能,請參閱 Thomas Steiner 撰寫的這篇color-scheme文章。除了黑色核取方塊輸入框,還有更多好處!

CSS accent-color

表單元素的 accent-color 在最近有新動作,這是一個 CSS 樣式,可變更瀏覽器輸入元素中使用的色調顏色。如要進一步瞭解這項功能,請前往 GitHub。我已將其納入此元件的樣式。由於瀏覽器支援此功能,因此我的核取方塊將以粉紅色和紫色突顯,更符合主題。

input[type="checkbox"] {
  accent-color: var(--brand);
}

Linux 版 Chromium 的粉紅色核取方塊螢幕截圖

使用固定漸層和內部焦點,營造醒目的色彩

顏色在節制使用時最能突顯,而我喜歡透過色彩繽紛的 UI 互動來達成這點。

上述影片中包含許多 UI 回饋和互動層,可透過以下方式為互動增添個人風格:

  • 醒目顯示背景資訊。
  • 提供 UI 意見回饋,說明值在範圍內的「完整度」。
  • 提供 UI 意見回饋,指出欄位正在接受輸入內容。

為了在使用者與元素互動時提供意見回饋,CSS 會使用 :focus-within 虛擬類別來變更各種元素的外觀,讓我們來分析 .fieldset-item,它非常有趣:

.fieldset-item {
  ...

  &:focus-within {
    background: var(--surface2);

    & svg {
      fill: white;
    }

    & picture {
      clip-path: circle(50%);
      background: var(--brand-bg-gradient) fixed;
    }
  }
}

當此元素的其中一個子項具有內部焦點時:

  1. .fieldset-item 背景會指派較高對比度的表面顏色。
  2. 巢狀 svg 會以白色填滿,以便提高對比度。
  3. 巢狀的 <picture> clip-path 會擴展為完整的圓形,背景則會以明亮的固定漸層填滿。

自訂範圍

以下是 HTML 輸入元素,我將示範如何自訂其外觀:

<input type="range">

我們需要自訂這個元素分為 3 個部分:

  1. 範圍元素/容器
  2. 追蹤
  3. 喜歡

範圍元素樣式

input[type="range"] {
  /* style setting variables */
  --track-height: .5ex;
  --track-fill: 0%;
  --thumb-size: 3ex;
  --thumb-offset: -1.25ex;
  --thumb-highlight-size: 0px;

  appearance: none;         /* clear styles, make way for mine */
  display: block;
  inline-size: 100%;        /* fill container */
  margin: 1ex 0;            /* ensure thumb isn't colliding with sibling content */
  background: transparent;  /* bg is in the track */
  outline-offset: 5px;      /* focus styles have space */
}

CSS 的前幾行是樣式的自訂部分,希望清楚標示這些部分有助於您瞭解。其餘樣式大多是重設樣式,可提供一致的基礎,用於建構元件的棘手部分。

追蹤樣式

input[type="range"]::-webkit-slider-runnable-track {
  appearance: none; /* clear styles, make way for mine */
  block-size: var(--track-height);
  border-radius: 5ex;
  background:
    /* hard stop gradient:
        - half transparent (where colorful fill we be)
        - half dark track fill
        - 1st background image is on top
    */
    linear-gradient(
      to right,
      transparent var(--track-fill),
      var(--surface1) 0%
    ),
    /* colorful fill effect, behind track surface fill */
    var(--brand-bg-gradient) fixed;
}

訣竅在於「顯示」鮮豔的填充顏色。這是透過頂端的硬停止漸層完成。漸層至填滿百分比為透明,之後會使用未填滿的軌跡顏色。未填滿的介面後面會使用全寬度顏色,正在等待透明顯示。

追蹤填滿樣式

我的設計確實需要 JavaScript,才能維持填滿樣式。只有 CSS 策略可以提供,但這些策略需要 thumb 元素的高度與音軌相同,而且在限制範圍內找不到和諧的組合。

/* grab sliders on page */
const sliders = document.querySelectorAll('input[type="range"]')

/* take a slider element, return a percentage string for use in CSS */
const rangeToPercent = slider => {
  const max = slider.getAttribute('max') || 10;
  const percent = slider.value / max * 100;

  return `${parseInt(percent)}%`;
};

/* on page load, set the fill amount */
sliders.forEach(slider => {
  slider.style.setProperty('--track-fill', rangeToPercent(slider));

  /* when a slider changes, update the fill prop */
  slider.addEventListener('input', e => {
    e.target.style.setProperty('--track-fill', rangeToPercent(e.target));
  })
})

我認為這會帶來不錯的視覺升級效果。滑桿在沒有 JavaScript 的情況下運作良好,不需要 --track-fill 屬性,如果沒有這個屬性,就不會顯示填滿樣式。如果可使用 JavaScript,請在填入自訂屬性時同時觀察任何使用者變更,並將自訂屬性與值同步。

Ana TudorCSS-Tricks 上發布了一篇很棒的文章,其中介紹了專為軌道填滿效果設計的 CSS 專屬解決方案。我還發現這個 range 元素非常有啟發性。

喜歡樣式

input[type="range"]::-webkit-slider-thumb {
  appearance: none; /* clear styles, make way for mine */
  cursor: ew-resize; /* cursor style to support drag direction */
  border: 3px solid var(--surface3);
  block-size: var(--thumb-size);
  inline-size: var(--thumb-size);
  margin-top: var(--thumb-offset);
  border-radius: 50%;
  background: var(--brand-bg-gradient) fixed;
}

這些樣式的大部分目的都是為了製作漂亮的圓形。您會再次看到固定背景漸層,這會統一縮圖、音軌和相關 SVG 元素的動態顏色。我將互動效果的樣式分開,以便隔離用於懸停醒目顯示的 box-shadow 技巧:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

::-webkit-slider-thumb {
  

  /* shadow spread is initally 0 */
  box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);

  /* if motion is OK, transition the box-shadow change */
  @media (--motionOK) {
    & {
      transition: box-shadow .1s ease;
    }
  }

  /* on hover/active state of parent, increase size prop */
  @nest input[type="range"]:is(:hover,:active) & {
    --thumb-highlight-size: 10px;
  }
}

這是易於管理,且會醒目顯示使用者意見的動畫圖像。 使用邊框陰影,我可以避免使用效果觸發版面配置。方法是建立不會模糊且符合指標元素的圓形形狀的陰影。接著變更並轉場動畫,滑鼠遊標懸停時移動大小。

如果核取方塊的醒目效果也能這麼簡單就好了…

跨瀏覽器的選取器

我發現需要使用以下 -webkit--moz- 選取器,才能確保跨瀏覽器的一致性:

input[type="range"] {
  &::-webkit-slider-runnable-track {}
  &::-moz-range-track {}
  &::-webkit-slider-thumb {}
  &::-moz-range-thumb {}
}

自訂核取方塊

以下是 HTML 輸入元素,我將示範如何自訂其外觀:

<input type="checkbox">

我們需要自訂這個元素分為 3 個部分:

  1. 核取方塊元素
  2. 相關聯的標籤
  3. 醒目顯示效果

核取方塊元素

input[type="checkbox"] {
  inline-size: var(--space-sm);   /* increase width */
  block-size: var(--space-sm);    /* increase height */
  outline-offset: 5px;            /* focus style enhancement */
  accent-color: var(--brand);     /* tint the input */
  position: relative;             /* prepare for an absolute pseudo element */
  transform-style: preserve-3d;   /* create a 3d z-space stacking context */
  margin: 0;
  cursor: pointer;
}

transform-styleposition 樣式會為稍後用於設定醒目顯示效果的擬似元素做好準備。否則,我大多會根據個人偏好風格進行修改。我希望游標是指標,我喜歡輪廓偏移,預設的核取方塊太小,如果支援 accent-color,請將這些核取方塊納入品牌配色方案。

核取方塊標籤

為核取方塊提供標籤很重要,原因有 2 個:第一個是表示核取方塊值的用途,回答「什麼?」第二項是針對使用者體驗,網頁使用者習慣透過相關聯的標籤與核取方塊互動。

輸入
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
標籤
<label for="text-notifications">
  <h3>Text Messages</h3>
  <small>Get notified about all text messages sent to your device</small>
</label>

在標籤上放置 for 屬性,以 ID <label for="text-notifications"> 指向核取方塊。請在核取方塊上按兩下名稱和 ID,確保可透過各種工具和技術 (例如滑鼠或螢幕閱讀器) 找到這項資訊:<input type="checkbox" id="text-notifications" name="text-notifications">:hover:active 等應用程式可透過連線免費取得,讓表單與使用者的互動方式更加順暢。

核取方塊醒目顯示

我想讓介面保持一致,而滑桿元素有精美的縮圖醒目效果,我想要與核取方塊搭配使用。縮圖可以使用 box-shadow 和其 spread 屬性,向上和向下縮放陰影。不過,由於我們的核取方塊是方形的,且應為方形,因此這項效果無法在此處運作。

我使用虛擬元素能夠達到相同的視覺效果,而且有相當大量的 CSS 供應商,可說是相當可觀:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

input[type="checkbox"]::before {
  --thumb-scale: .01;                        /* initial scale of highlight */
  --thumb-highlight-size: var(--space-xl);

  content: "";
  inline-size: var(--thumb-highlight-size);
  block-size: var(--thumb-highlight-size);
  clip-path: circle(50%);                     /* circle shape */
  position: absolute;                         /* this is why position relative on parent */
  top: 50%;                                   /* pop and plop technique (https://web.dev/centering-in-css#5-pop-and-plop) */
  left: 50%;
  background: var(--thumb-highlight-color);
  transform-origin: center center;            /* goal is a centered scaling circle */
  transform:                                  /* order here matters!! */
    translateX(-50%)                          /* counter balances left: 50% */
    translateY(-50%)                          /* counter balances top: 50% */
    translateZ(-1px)                          /* PUTS IT BEHIND THE CHECKBOX */
    scale(var(--thumb-scale))                 /* value we toggle for animation */
  ;
  will-change: transform;

  @media (--motionOK) {                       /* transition only if motion is OK */
    & {
      transition: transform .2s ease;
    }
  }
}

/* on hover, set scale custom property to "in" state */
input[type="checkbox"]:hover::before {
  --thumb-scale: 1;
}

建立圓形擬似元素的工作很簡單,但將其置於所附加元素的後方則較為困難。以下是修正前後的畫面:

這當然是微互動的,但我必須保持視覺一致性。動畫縮放技術與我們在其他地方使用的相同。我們將自訂屬性設為新值,並讓 CSS 根據動畫偏好設定進行轉場。這裡的重點功能是 translateZ(-1px)。父項建立了 3D 空間,而這個子項的疑似元素會將自身稍微向後放置在 z 空間中,以便存取該空間。

無障礙設定

YouTube 影片詳細示範了滑鼠、鍵盤和螢幕閱讀器對這項設定元件的互動方式。我會在這裡說明一些詳細資訊。

HTML 元素選擇

<form>
<header>
<fieldset>
<picture>
<label>
<input>

每個參數都包含使用者瀏覽工具的提示和訣竅。有些元素會提供互動提示,有些會連結互動功能,有些則有助於塑造螢幕閱讀器瀏覽的無障礙樹狀結構。

HTML 屬性

我們可以隱藏螢幕閱讀器不需要的元素,在本例中是滑桿旁邊的圖示:

<picture aria-hidden="true">

以上影片示範 Mac OS 上的螢幕閱讀器流程。請注意,輸入焦點如何從一個滑桿直接移動到下一個滑桿。這是因為我們已隱藏可能會在前往下一個滑桿的路程中停下來的圖示。如未提供這項屬性,使用者就必須暫停、聆聽,然後略過可能看不到的圖片。

SVG 是一系列數學運算,讓我們新增 <title> 元素,為滑鼠游標提供免費標題,並提供可供人類閱讀的註解,說明數學運算所產生的內容:

<svg viewBox="0 0 24 24">
  <title>A note icon</title>
  <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>

除此之外,我們使用了足夠清楚標示的 HTML,讓表單在滑鼠、鍵盤、電玩遊戲控制器和螢幕閱讀器之間都能順利測試。

JavaScript

已經說明如何透過 JavaScript 管理軌跡填充顏色,現在讓我們來看看 <form> 相關的 JavaScript:

const form = document.querySelector('form');

form.addEventListener('input', event => {
  const formData = Object.fromEntries(new FormData(form));
  console.table(formData);
})

每當使用者與表單互動並變更表單時,控制台就會將表單以物件形式記錄到資料表中,方便您在提交至伺服器前進行查看。

Console.table() 結果的螢幕截圖,其中表單資料會顯示在表格中

結論

既然你知道我如何做到,你會怎麼做呢?這可以提供一些有趣的元件架構!誰會在自己偏好的架構中,製作第一個含有空格的版本?🙂

讓我們多方嘗試,瞭解在網路上建構應用程式的所有方式。請製作示範作品,並在推文中附上連結,我會將其加入下方的「社群重混」專區!

社群重混作品

  • @tomayac 的樣式,與核取方塊標籤的懸停區域相關!這個版本的元素之間沒有懸停間距:demosource