設定コンポーネントの作成

スライダーとチェックボックスの設定コンポーネントを作成する方法の基本的な概要。

この記事では、レスポンシブで、複数のデバイス入力をサポートし、ブラウザ間で動作する、ウェブ用の設定コンポーネントの構築について考えを共有します。デモをお試しください。

デモ

動画で確認したい場合や、作成中の UI/UX をプレビューしたい場合は、YouTube の短いチュートリアルをご覧ください。

概要

このコンポーネントの要素は、次のセクションに分類されています。

  1. レイアウト
  2. カスタム範囲の入力
  3. カスタム チェックボックス入力
  4. ユーザー補助に関する考慮事項
  5. JavaScript

レイアウト

これは、すべて CSS グリッドで作成された最初の GUI チャレンジのデモです。グリッド用の Chrome DevTools でハイライト表示された各グリッドは次のとおりです。

設定レイアウトを構成するすべてのボックスを表示するために役立つ、カラフルなアウトラインと隙間の間隔のオーバーレイ

ギャップのみ

最も一般的なレイアウト:

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

グリッドを使ってブロック間に隙間を追加するだけなので、このレイアウトを「ギャップの分量だけ」と呼んでいます。

この戦略を使用するレイアウトは 5 つあります。以下にすべてを示します。

枠線でハイライト表示された縦型グリッド レイアウトと、ギャップが塗りつぶされたレイアウト

各入力グループ(.fieldset-item)を含む fieldset 要素は、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 とグリッドの両方で align-items または align-content の機能が提供されており、ラップ要素を処理する場合、content レイアウトの配置により、子要素間でスペースがグループとして分散されます。

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

メイン要素は place-content: center配置の省略形を使用しているため、子要素は 1 列と 2 列の両方のレイアウトで垂直方向と水平方向の中央に配置されます。

上記の動画で、折り返しが発生しても「コンテンツ」が中央に配置されたままになる様子をご覧ください。

自動調整の最小値と最大値を繰り返す

<form> は、各セクションにアダプティブ グリッド レイアウトを使用します。このレイアウトでは、使用可能なスペースに応じて列が 1 列から 2 列に切り替わります。

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 プロパティは、repeat()minmax()min() の 3 つの CSS 関数を使用します。Una Kravets が、この問題について優れたレイアウトのブログ投稿を公開しています。RAM という名前で呼ばれています。

Una のレイアウトと比較すると、このレイアウトには 3 つの特別な追加要素があります。

  • 追加の min() 関数を渡します。
  • align-items: flex-start を指定します。
  • max-width: 89vw スタイルがあります。

追加の min() 関数については、Evan Minto のブログ投稿「Intrinsically Responsive CSS Grid with minmax() and 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 は非常に優れています。

前述の「ギャップ用」のレイアウトを思い出してください。このコンポーネントでの表示のより詳細なバージョンを以下に示します。

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 ウェブページのスクリーンショット(Color 2: Perception エピソードが開いている)
知覚色(その他)については、CSS ポッドキャストをご覧ください

このデモでは本日の構文と ライトとダークにするために切り替える値に注目しますサーフェスとテキストの色を 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 ではこれらの色にアクセスできません。繰り返しますが、ほとんどの最新モニタでは、色の 3 分の 1 にアクセスできません。しかも、ただの色ではなく、画面で表示できる最も鮮やかな色です。モニター ハードウェアの進化が CSS の仕様とブラウザの実装よりも速いため、ウェブサイトが白っぽくなっています。

Lea Verou

カラーパターンを使用した適応型フォーム コントロール

多くのブラウザにはダークモードのコントロールが搭載されています(現在は Safari と Chromium)。ただし、デザインでダークモードを使用する場合は、CSS または HTML で指定する必要があります。

上記は、DevTools の [スタイル] パネルからプロパティの効果を示しています。このデモでは 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 のみの戦略もありますが、その場合はサムネイル要素のサイズがトラックと同じになる必要があります。この制限内で調和させる方法を見つけることができませんでした。

/* 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 Tudor による CSS-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-style スタイルと position スタイルは、後でハイライトのスタイル設定に使用する疑似要素の準備です。それ以外は 私個人の軽微なスタイルですカーソルはポインタにしたい、輪郭のオフセットは欲しい、デフォルトのチェックボックスは小さすぎる、accent-colorサポートされている場合は、これらのチェックボックスをブランドのカラーパターンに統合したい。

チェックボックス ラベル

チェックボックスにラベルを付けることは、次の 2 つの理由から重要です。1 つ目は、チェックボックスの値が何に使用されているかを表し、「オンまたはオフの対象」を回答することです。2 つ目は UX です。ウェブユーザーは、関連するラベルを使用してチェックボックスを操作することに慣れています。

入力
<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>

ラベルに、ID: <label for="text-notifications"> のチェックボックスを参照する for 属性を追加します。チェックボックスの名前と ID の両方を 2 回入力して、マウスやスクリーンリーダーなど、さまざまなツールやテクノロジーで検出できるようにします。<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 が、チェックボックスのラベルのホバー領域に関するスタイルを投稿しました。このバージョンでは、要素(デモソース)の間にホバー ギャップがありません。