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

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

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

デモ

動画で確認したい場合や、作成中の 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 の値が column-gap とは異なる(--space-xl と --space-xxl)ため、レスポンシブ レイアウトにカスタム タッチを加えています。列を積み重ねる場合は、広い間隔が必要ですが、ワイド画面の場合ほど広くはありません。

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

Una のレイアウトと比較すると、Google のレイアウトには 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 が、チェックボックスのラベルのホバー領域に関するスタイルを投稿しました。このバージョンでは、要素(デモソース)の間にホバー ギャップがありません。