Vue 3でアクセス可能なトグルスイッチを構築する方法


あなたがアプリケーションで見つけることができるさまざまなUIコンポーネントがたくさんあります.あなたが最も見つけるだろうものは、おそらくボタン、入力、フォーム、または画像です.彼らはとても一般的であるので、HTMLさえ彼らのためにデフォルト要素を提供します!しかし、それはあなた自身で構築する必要がありますあまり知られていないコンポーネントに遭遇することはまれではない.
最近、スイッチ/トグルのコンポーネントについて最近、このように見えます.

最初は、単純なUIコンポーネントのようです.しかし、それはすべての異なる状態、そのアクセシビリティ、およびその再利用性を考慮する必要があるときにトリッキーでありえます.
さて、幸運なあなた!今日、私はあなたと同じようにする方法を共有するつもりです.
Note : Vueを使ってビルドしていますが、コンセプトは簡単に他のフレームワークにも適用できます.また、私はVue 3を使用していますが、怖がらないでください.ここでVue 2との多くの違いはありません!😉

少しの概念


あなたのキーボードに直接ジャンプする前にコーディングを開始する前に、特にスイッチの目的を考慮する必要があります.実際、スイッチは2つの方法で使用できます.
  • (スイッチなどで)点灯する
  • つの選択肢(テーマスイッチャなど)を選択するには
  • それは基本的な実装に影響を与えるので、ユースケースがあなたのために正しいものであるかを知ることが不可欠です.私たちの場合、我々は最初のユースケースに行くつもりです.
    また、スイッチをトグルした場合、何が起こるかをユーザが知っているようにトグルスイッチをラベルしたい.
    ユーザーがニュースレターを受け取ることを選択する現実世界の例を見てみましょう.あなたが使用する最初の実装を考えることができますchecked プロップ, Alabel プロップとAtoggle イベント
    <Switch
      :checked="shouldReceiveNewsletter"
      @toggle="toggle"
      label="Receive newsletter"
    />
    
    それで結構です.しかし、我々は同じ結果を v-model 以下のように:
    <Switch v-model:checked="shouldReceiveNewsletter" label="Receive newsletter" />
    
    あなたがVueに精通しているならば、あなたはv-model 上の例では.Vue 3によって導入された変更の1つです.テンプレートに直接引数名を使用することができます.上のコードはchecked プロップ<Switch> あなたがAを放出することによって更新することができますupdate:checked イベント.

    ビルドテンプレート


    HTML要素を選択する必要があるときは、意味を持つものを選択的に選択する必要があります.我々のケースでは、我々は input 我々がコントロールを構築しているように.スイッチの2つのユースケースがあります.
  • スイッチの設定をオン/オフ:我々は必要がありますcheckbox
  • つのオプションと別の(光/暗いテーマのような)間の切り替え:我々は2つが必要ですradio buttons
  • また、入力が正しくラベルされていることを確認する必要があります.そうする一つの方法は<input> イン<label> テキストを追加します.
    最後に、空を加えることもできます<span> 私たちは私たちのトグルスイッチを構築するために後で使用するつもりです.次へ進むSwitch.vue 以下を貼り付けるファイル
    <template>
      <label>
        <input type="checkbox" />
        <span></span>
        <span>{{ label }}</span>
      </label>
    </template>
    

    小道具とV型


    スイッチに二つの小道具を渡す必要があります.label は、checked はbooleanです.覚えておいてくださいchecked 支柱はv-model:checked :
    <template>
      <label>
        <input
          type="checkbox"
          :checked="checked"
          @change="$emit('update:checked', $event.target.checked)"
        />
        <span></span>
        <span>{{ label }}</span>
      </label>
    </template>
    
    <script>
      export default {
        name: "Switch",
        props: {
          label: {
            type: String,
            required: true,
          },
          checked: {
            type: Boolean,
            required: true,
          },
        },
      };
    </script>
    
    上記の入力に問題があります.実際、ルート要素は基になる入力とは異なる.入力に渡す他の属性に対して追加の支柱を作成しなければなりませんdisabled , 例えば).
    それを修正するには v-bind="$attrs" 入力とdisable attribute inheritance ルート要素について:
    <input
      v-bind="$attrs"
      type="checkbox"
      @change="$emit('update:checked', $event.target.checked)"
      :checked="checked"
    />
    
    <script>
      export default {
        name: "Switch",
        inheritAttrs: false,
        /* ... */
      };
    </script>
    

    コンポーネントのスタイル


    容器とラベル


    これまでのところ、スイッチは次のようになります.

    それに直面しましょう、それは地獄として醜いです.そのために、CSSクラスを異なる要素に追加します.
    <template>
      <label class="container">
        <input
          v-bind="$attrs"
          class="input"
          type="checkbox"
          :checked="checked"
          @change="$emit('update:checked', $event.target.checked)"
        />
        <span class="switch"></span>
        <span class="label">{{ label }}</span>
      </label>
    </template>
    
    私たちは一人ずつやります.第一に.container . 我々はテキストがスイッチの右側にあることを知っています、そして、我々はそれを完全に中心にして欲しいです.また、全体のトグルをクリック可能にしたいので、ポインタカーソルを追加しましょう.
    <style scoped>
      .container {
        cursor: pointer;
        display: flex;
        align-items: center;
      }
    </style>
    
    また、ラベルにきれいな色を与え、チェックボックスからいくつかのスペースを与えます.
    .label {
      margin-left: 12px;
      color: #1a202c;
    }
    
    それでは、私たちは<input> 意味的な理由のために、それは視覚に関して我々に役に立たないでしょう.視覚的に隠す必要がありますが、まだアクセシビリティの理由でDOMに保管しなければなりません.
    /* Visually hide the checkbox input */
    .input {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border-width: 0;
    }
    
    注意:プロパティは.sr-only クラスからTailwind CSS

    スイッチ


    スイッチは丸みを帯びた容器要素で構成される.この円は、入力がチェックされたかどうかによって左または右に移動します.
    スクリーンショットを見ると、内側の円がコンテナ要素の約半分のサイズであることがわかります.容器の幅は高さの2倍ある.CSSカスタムプロパティを利用しましょう.
    .switch {
      --switch-container-width: 50px;
      --switch-size: calc(var(--switch-container-width) / 2);
    }
    
    内側のサークルを作成するには::before トリックトリック.容器の中に作るには、容器Aを与える必要がありますrelative 位置と内側の円absolute 位置.
    さらに、内側の円は、ほぼ100のサイズでなければなりません--switch-size しかし、コンテナをオーバーフローしてはいけません.私たちは calc 関数を調整する
    .switch {
      --switch-container-width: 50px;
      --switch-size: calc(var(--switch-container-width) / 2);
    
      /* Vertically center the inner circle */
      display: flex;
      align-items: center;
      position: relative;
      height: var(--switch-size);
      flex-basis: var(--switch-container-width);
      /* Make the container element rounded */
      border-radius: var(--switch-size);
      background-color: #e2e8f0;
    }
    
    .switch::before {
      content: "";
      position: absolute;
      /* Move a little bit the inner circle to the right */
      left: 1px;
      height: calc(var(--switch-size) - 4px);
      width: calc(var(--switch-size) - 4px);
      /* Make the inner circle fully rounded */
      border-radius: 9999px;
      background-color: white;
    }
    
    結果を以下に示します.

    それはいいですが、スイッチをクリックすると何も起こりません.少なくとも、視覚的に.確かに、入力は正しくチェックされますが、あなたのスイッチはそれに接続されていません!
    これらの変更を反映するためには、CSS adjacent sibling selector , は+ , 異なる入力状態に従ってスイッチを設定する.たとえば、チェックボックスをチェックすると :checked 擬似クラスが追加されました.では、それを使いましょう.
    .input:checked + .switch {
      /* Teal background */
      background-color: #4fd1c5;
    }
    
    .input:checked + .switch::before {
      border-color: #4fd1c5;
      /* Move the inner circle to the right */
      transform: translateX(
        calc(var(--switch-container-width) - var(--switch-size))
      );
    }
    
    スイッチがある状態から別の状態へ移動する方法は滑らかではない.遷移を加える必要があるtransform and background-color 修正するには:
    .switch {
      /* ... */
      transition: background-color 0.25s ease-in-out;
    }
    
    .switch::before {
      /* ... */
      transition: transform 0.375s ease-in-out;
    }
    

    合焦状態


    今、あなたは動作するスイッチを持つ必要があります.しかし、仕事は完全にまだ行われていない!実際、まだ実装されていない入力には、異なる状態があります.たとえば、あなたが押すとTab キーは、スイッチを集中するには、それが適切に集中している任意の視覚的なフィードバックを持っていない.無効な入力も同様です.
    最初のステップとして、追加のCSSカスタムプロパティを.switch を返します.
    .switch {
      /* ... */
    
      --light-gray: #e2e8f0;
      --gray: #cbd5e0;
      --dark-gray: #a0aec0;
      --teal: #4fd1c5;
      --dark-teal: #319795;
    
      /* ... */
      background-color: var(--light-gray);
    }
    
    .input:checked + .switch {
      background-color: var(--teal);
    }
    
    .input:checked + .switch::before {
      border-color: var(--teal);
      /* ... */
    }
    
    注:色はTailwind CSS あなたが疑問に思っている場合.
    フォーカス状態に取り組みましょう.私たちは複雑なUIワイズを行うつもりはありません.
    .switch::before {
      /* ... */
      border: 2px solid var(--light-gray);
    }
    
    ここでは、スイッチコンテナの背景と同じ色を選びました.実際、最初に、我々は内側の円の境界色を背景色と混同したい.そのように、我々は異なるを追加するときborder-color フォーカス状態のために、我々はそれを見ることができます.我々は、暗いものを加えるつもりですborder-color when the input is focused:
    .input:focus + .switch::before {
      border-color: var(--dark-gray);
    }
    
    .input:focus:checked + .switch::before {
      border-color: var(--dark-teal);
    }
    
    以下のようになります.

    無効な状態については、内側の円をグレーで埋め、スイッチコンテナーを暗くして、何もできないことを示します.
    .input:disabled + .switch {
      background-color: var(--gray);
    }
    
    .input:disabled + .switch::before {
      background-color: var(--dark-gray);
      border-color: var(--dark-gray);
    }
    
    以下に、無効なスイッチのように見えます.

    スイッチの応答性


    我々は、チェックする1つの最後のものを持っています.以下のスクリーンショットを見てください.

    長いラベルがある場合は、テキストがスイッチに溢れ、複数行をとることができます.それは反応しない、それ?我々のスイッチが縮小できないことを確認してください、そして、ラベルは1つ以上の線をとらないでしょう
    .switch {
      /* ... */
      /* In case the label gets long, the toggle shouldn't shrink. */
      flex-shrink: 0;
    }
    
    .label {
      /* ... */
      /* Show an ellipsis if the text takes more than one line */
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    
    さて、スイッチは応答します.

    ベール!我々はスイッチのために完了しました.スイッチはUIの小さな部分ですが、実装するのは簡単ではありません.このようなコンポーネントを構築する際の主な取材です.
  • コンポーネントのAPIをあらかじめ考えてください.あなたがビルドしたいもののより良いビジョンがあるでしょう.
  • UIコンポーネントのさまざまな制約を考えることを忘れないでください.アクセシビリティ、様々な状態、その応答性など.
  • コンポーネントを段階的にビルドします.これらの種類のコンポーネントを使用して反復的なアプローチを行う必要があります.
  • 場合には、完全なコードを持っている場合、私はあなたが下に見つけることができるgithubのgistにそれを置く.どのように良いですか?😄
    < div >