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

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

この投稿では、レスポンシブで、複数のデバイス入力をサポートし、複数のブラウザで動作するウェブ用の設定コンポーネントの作成について考えたいと思います。デモをお試しください。

デモ

動画、または開発中の UI/UX のプレビューをご覧になりたい場合は、YouTube の簡単なチュートリアルをご覧ください。

概要

このコンポーネントを以下のセクションに分けます。

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

レイアウト

これは、すべての CSS グリッドになる最初の GUI チャレンジ デモです。以下に、Chrome DevTools for grid でハイライト表示した各グリッドを示します。

カラフルな輪郭とギャップ間隔のオーバーレイにより、設定のレイアウトを構成するすべてのボックスを表示できます。

空白のみ

最も一般的なレイアウトは次のとおりです。

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 レスポンシブ 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 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 はこれらの色にまったくアクセスできません。 繰り返しになりますが、最近のほとんどのモニターでは、全体の 3 分の 1 の色を使用できません。これらは単なる色ではなく、画面で表示できる最も鮮やかな色です。モニター ハードウェアは、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 にしかない戦略もありますが、それらでは親指要素の高さがトラックと同じであることが必要で、それらの制限内でハーモニーを見つけることができませんでした。

/* 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-shadowspread プロパティを使用してシャドウを上下に拡大できました。しかし、この効果はここでは機能しません。チェックボックスは正方形であり、正方形であるべきだからです。

疑似要素と、複雑な 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 空間に少し戻ることで 3D 空間を利用しました。

ユーザー補助

この YouTube 動画では、この設定コンポーネントのマウス、キーボード、スクリーン リーダーの操作について、わかりやすいデモが提供されています。ここで詳細を説明します

HTML 要素の選択

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

それぞれに、ユーザーのブラウジング ツールに関するヒントやヒントが含まれています。操作のヒントを提供する要素、インタラクティビティを連携させる要素、スクリーン リーダーのナビゲーション ツリーを形成する要素などがあります。

HTML 属性

スクリーン リーダーで不要な要素(この場合はスライダーの横にあるアイコン)を非表示にできます。

<picture aria-hidden="true">

上の動画では、Mac OS でのスクリーン リーダーのフローを説明しています。入力フォーカスが 1 つのスライダーから次のスライダーに直線的に移動する仕組みに注目してください。これは、次のスライダーに到達するまでに経由地を示すアイコンが非表示になっているためです。この属性がないと、ユーザーはカメラを止め、聞き取り、画像から目を逸らさなければならず、画像を見ることができない可能性があります。

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 の要素の間にマウスオーバーのギャップはありません。