Reactでカスタムトグルスイッチを構築する方法
序章
Webアプリケーションの構築には、通常、ユーザーとの対話のための準備が含まれます。 ユーザーとの対話を準備する重要な方法の1つは、フォームを使用することです。 ユーザーからさまざまな種類の入力を受け取るために、さまざまなフォームコンポーネントが存在します。 たとえば、パスワードコンポーネントは、ユーザーから機密情報を取得し、それが表示されないようにマスクします。
ほとんどの場合、ユーザーから取得する必要のある情報は boolean-like です。たとえば、yesまたはno、trueまたは false 、enableまたはdisable、onまたはoffなど。 従来、チェックボックスフォームコンポーネントは、これらの種類の入力を取得するために使用されます。 ただし、最近のインターフェイスデザインでは、アクセシビリティの問題がいくつかありますが、チェックボックスの代わりにトグルスイッチが一般的に使用されています。
このチュートリアルでは、Reactを使用してカスタムトグルスイッチコンポーネントを構築する方法を説明します。 チュートリアルの最後に、カスタムトグルスイッチコンポーネントを使用するデモReactアプリがあります。
これは、このチュートリアルで構築する最終的なアプリケーションのデモです。
前提条件
始める前に、次のものが必要です。
- Node.jsおよびnpmバージョン5.2以降がマシンにインストールされています。 Nodeをインストールしてnpmのバージョンを確認するには、ご使用の環境のNode.jsのインストール方法とローカル開発環境の作成方法ガイドを参照してください。 npm 5.2以降を使用すると、
npx
コマンドを利用できるようになります。npx
を使用すると、パッケージをグローバルにダウンロードせずにcreate-react-app
を実行できます。 - このチュートリアルは、あなたがすでにReactに精通していることを前提としています。 そうでない場合は、 React.js チュートリアルシリーズのコーディング方法を確認するか、Reactドキュメントを読んでReactの詳細を確認してください。
ステップ1—はじめに
開始するには、npx
とcreate-react-app
を使用して新しいReactアプリケーションを作成します。 アプリケーションには任意の名前を付けることができますが、このチュートリアルではreact-toggle-switchを使用します。
npx create-react-app react-toggle-switch
次に、アプリケーションに必要な依存関係をインストールします。 ターミナルウィンドウを使用して、プロジェクトディレクトリに移動します。
cd react-toggle-switch
次のコマンドを実行して、必要な依存関係をインストールします。
npm install [email protected] [email protected] [email protected] [email protected] [email protected]
注:最小サポートのクイックガイドを参照して、インストールするnode-sass
のバージョンがご使用の環境と互換性があることを確認してください。
デフォルトのスタイルが必要になるため、アプリケーションの依存関係としてbootstrap
パッケージをインストールしました。 アプリケーションにブートストラップを含めるには、src/index.js
ファイルを編集し、他のすべてのimport
ステートメントの前に次の行を追加します。
src / index.js
import "bootstrap/dist/css/bootstrap.min.css";
npm
を指定して次のコマンドを実行し、アプリケーションを起動します。
npm start
アプリケーションを起動すると、開発を開始できます。 ライブリロード機能を備えたブラウザタブが開いていることに注意してください。 ライブリロードは、開発中にアプリケーションへの変更と同期し続けます。
この時点で、アプリケーションビューは次のスクリーンショットのようになります。
次に、トグルコンポーネントを作成します。
ステップ2—ToggleSwitch
コンポーネントを作成する
コンポーネントをビルドする前に、プロジェクトのsrc
ディレクトリ内にcomponents
という名前の新しいディレクトリを作成します。
mkdir -p src/components
次に、components
ディレクトリ内にToggleSwitch
という名前の別の新しいディレクトリを作成します。
mkdir -p src/components/ToggleSwitch
src/components/ToggleSwitch
内に、index.js
とindex.scss
の2つの新しいファイルを作成します。 index.js
ファイルを作成して、お気に入りのテキストエディタで開きます。
nano src/components/ToggleSwitch/index.js
次のコンテンツをsrc/components/ToggleSwitch/index.js
ファイルに追加します。
src / components / ToggleSwitch / index.js
import PropTypes from 'prop-types'; import classnames from 'classnames'; import isString from 'lodash/isString'; import React, { Component } from 'react'; import isBoolean from 'lodash/isBoolean'; import isFunction from 'lodash/isFunction'; import './index.scss'; class ToggleSwitch extends Component {} ToggleSwitch.propTypes = { theme: PropTypes.string, enabled: PropTypes.oneOfType([ PropTypes.bool, PropTypes.func ]), onStateChanged: PropTypes.func } export default ToggleSwitch;
このコードスニペットでは、ToggleSwitch
コンポーネントを作成し、その小道具のいくつかに型チェックを追加しました。
theme
:トグルスイッチのスタイルと色を示すstring
です。enabled
:boolean
またはboolean
を返すfunction
のいずれかで、レンダリング時のトグルスイッチの状態を決定します。onStateChanged
:トグルスイッチの状態が変化したときに呼び出されるコールバック関数です。 これは、スイッチが切り替えられたときに親コンポーネントでアクションをトリガーする場合に役立ちます。
ToggleSwitch状態の初期化
次のコードスニペットでは、ToggleSwitch
コンポーネントの状態を初期化し、トグルスイッチの状態を取得するためのいくつかのコンポーネントメソッドを定義します。
src / components / ToggleSwitch / index.js
// ... class ToggleSwitch extends Component { state = { enabled: this.enabledFromProps() } isEnabled = () => this.state.enabled enabledFromProps() { let { enabled } = this.props; // If enabled is a function, invoke the function enabled = isFunction(enabled) ? enabled() : enabled; // Return enabled if it is a boolean, otherwise false return isBoolean(enabled) && enabled; } } // ...
ここで、enabledFromProps()
メソッドは、渡されたenabled
小道具を解決し、レンダリング時にスイッチを有効にする必要があるかどうかを示すboolean
を返します。 enabled
propがboolean
の場合、ブール値を返します。 function
の場合、戻り値がboolean
であるかどうかを判断する前に、まず関数を呼び出します。 それ以外の場合は、false
を返します。
enabledFromProps()
からの戻り値を使用して初期のenabled
状態を設定したことに注意してください。 また、isEnabled()
メソッドを追加して、現在のenabled
状態を取得しました。
ToggleSwitchの切り替え
先に進んで、クリックされたときにスイッチを切り替えるメソッドを追加しましょう。 次のコードをファイルに追加します。
src / components / ToggleSwitch / index.js
// ... class ToggleSwitch extends Component { // ...other class members here toggleSwitch = evt => { evt.persist(); evt.preventDefault(); const { onClick, onStateChanged } = this.props; this.setState({ enabled: !this.state.enabled }, () => { const state = this.state; // Augument the event object with SWITCH_STATE const switchEvent = Object.assign(evt, { SWITCH_STATE: state }); // Execute the callback functions isFunction(onClick) && onClick(switchEvent); isFunction(onStateChanged) && onStateChanged(state); }); } } // ...
このメソッドはclick
イベントリスナーとしてトリガーされるため、evt
パラメーターを使用して宣言しました。 まず、このメソッドは、論理NOT
(!
)演算子を使用して、現在のenabled
状態を切り替えます。 状態が更新されると、onClick
およびonStateChanged
プロップに渡されるコールバック関数がトリガーされます。
onClick
は最初の引数としてイベントを必要とするため、新しい状態オブジェクトを含む追加のSWITCH_STATE
プロパティでイベントを拡張したことに注意してください。 ただし、onStateChanged
コールバックは、新しい状態オブジェクトで呼び出されます。
ToggleSwitchのレンダリング
最後に、ToggleSwitch
コンポーネントのrender()
メソッドを実装しましょう。 次のコードをファイルに追加します。
src / components / ToggleSwitch / index.js
// ... class ToggleSwitch extends Component { // ...other class members here render() { const { enabled } = this.state; // Isolate special props and store the remaining as restProps const { enabled: _enabled, theme, onClick, className, onStateChanged, ...restProps } = this.props; // Use default as a fallback theme if valid theme is not passed const switchTheme = (theme && isString(theme)) ? theme : 'default'; const switchClasses = classnames( `switch switch--${switchTheme}`, className ) const togglerClasses = classnames( 'switch-toggle', `switch-toggle--${enabled ? 'on' : 'off'}` ) return ( <div className={switchClasses} onClick={this.toggleSwitch} {...restProps}> <div className={togglerClasses}></div> </div> ) } } // ...
このrender()
メソッドでは多くのことが行われているので、少し分解してみましょう。
- まず、
enabled
状態がコンポーネント状態から分解されます。 - 次に、コンポーネントの小道具を分解し、スイッチに渡される
restProps
を抽出します。 これにより、コンポーネントの特別な小道具を傍受して分離することができます。 - 次に、 classnames を使用して、コンポーネントの
theme
とenabled
の状態に基づいて、スイッチと内部トグルのクラスを作成します。 - 最後に、適切な小道具とクラスを使用してDOM要素をレンダリングします。 スイッチの
click
イベントリスナーとしてthis.toggleSwitch
を渡したことに注意してください。
ファイルを保存して閉じます。
これで、ToggleSwitch
が作成されました。
ステップ3—ToggleSwitch
のスタイリング
ToggleSwitch
コンポーネントとそれに必要な機能が揃ったので、先に進んでそのスタイルを記述できます。
お気に入りのテキストエディタでindex.scss
ファイルを開きます。
nano src/components/ToggleSwitch/index.scss
次のコードスニペットをファイルに追加します。
src / components / ToggleSwitch / index.scss
// DEFAULT COLOR VARIABLES $ball-color: #ffffff; $active-color: #62c28e; $inactive-color: #cccccc; // DEFAULT SIZING VARIABLES $switch-size: 32px; $ball-spacing: 2px; $stretch-factor: 1.625; // DEFAULT CLASS VARIABLE $switch-class: 'switch-toggle'; /* SWITCH MIXIN */ @mixin switch($size: $switch-size, $spacing: $ball-spacing, $stretch: $stretch-factor, $color: $active-color, $class: $switch-class) {}
ここでは、いくつかのデフォルト変数を定義し、switch
ミックスインを作成しました。 次のセクションでは、ミックスインを実装しますが、最初に、switch
ミックスインのパラメーターを調べてみましょう。
$size
:スイッチ要素の高さ。 長さの単位が必要です。 デフォルトは32px
です。$spacing
:丸いボールとスイッチコンテナの間のスペース。 長さの単位が必要です。 デフォルトは2px
です。$stretch
:スイッチ要素の幅を伸ばす範囲を決定するために使用される係数。 単位のない数でなければなりません。 デフォルトは1.625
です。$color
:アクティブ状態のときのスイッチの色。 これは有効な色の値である必要があります。 この色に関係なく、円形のボールは常に白であることに注意してください。$class
:スイッチを識別するための基本クラス。 これは、スイッチの状態クラスを動的に作成するために使用されます。 デフォルトは'switch-toggle'
です。 したがって、デフォルトの状態クラスは.switch-toggle--on
および.switch-toggle--off
です。
スイッチミックスインの実装
switch
ミックスインの実装は次のとおりです。
src / components / ToggleSwitch / index.scss
// ... @mixin switch($size: $switch-size, $spacing: $ball-spacing, $stretch: $stretch-factor, $color: $active-color, $class: $switch-class) { // SELECTOR VARIABLES $self: '.' + $class; $on: #{$self}--on; $off: #{$self}--off; // SWITCH VARIABLES $active-color: $color; $switch-size: $size; $ball-spacing: $spacing; $stretch-factor: $stretch; $ball-size: $switch-size - ($ball-spacing * 2); $ball-slide-size: ($switch-size * ($stretch-factor - 1) + $ball-spacing); // SWITCH STYLES height: $switch-size; width: $switch-size * $stretch-factor; cursor: pointer !important; user-select: none !important; position: relative !important; display: inline-block; &#{$on}, &#{$off} { &::before, &::after { content: ''; left: 0; position: absolute !important; } &::before { height: inherit; width: inherit; border-radius: $switch-size / 2; will-change: background; transition: background .4s .3s ease-out; } &::after { top: $ball-spacing; height: $ball-size; width: $ball-size; border-radius: $ball-size / 2; background: $ball-color !important; will-change: transform; transition: transform .4s ease-out; } } &#{$on} { &::before { background: $active-color !important; } &::after { transform: translateX($ball-slide-size); } } &#{$off} { &::before { background: $inactive-color !important; } &::after { transform: translateX($ball-spacing); } } }
このミックスインでは、ミックスインに渡されたパラメーターに基づいていくつかの変数を設定することから始めます。 次に、スタイルを作成します。 ::after
および::before
疑似要素を使用して、スイッチのコンポーネントを動的に作成していることに注意してください。 ::before
はスイッチコンテナを作成し、::after
は円形ボールを作成します。
また、基本クラスから状態クラスを構築し、それらを変数に割り当てる方法にも注目してください。 $on
変数は有効状態のセレクターにマップされ、$off
変数は無効状態のセレクターにマップされます。
また、スタイルを使用できるようにするには、基本クラス(.switch-toggle
)を状態クラス(.switch-toggle--on
または.switch-toggle--off
)と一緒に使用する必要があることを確認しました。 したがって、&#{$on}
および&#{$off}
セレクターを使用しました。
テーマスイッチの作成
switch
ミックスインができたので、トグルスイッチ用にいくつかのテーマスタイルを作成し続けます。 default
とgraphite-small
の2つのテーマを作成します。
次のコードスニペットをsrc/components/ToggleSwitch/index.scss
ファイルに追加します。
src / components / ToggleSwitch / index.scss
// ... @function get-switch-class($selector) { // First parse the selector using `selector-parse` // Extract the first selector in the first list using `nth` twice // Extract the first simple selector using `simple-selectors` and `nth` // Extract the class name using `str-slice` @return str-slice(nth(simple-selectors(nth(nth(selector-parse($selector), 1), 1)), 1), 2); } .switch { $self: &; $toggle: #{$self}-toggle; $class: get-switch-class($toggle); // default theme &#{$self}--default > #{$toggle} { // Always pass the $class to the mixin @include switch($class: $class); } // graphite-small theme &#{$self}--graphite-small > #{$toggle} { // A smaller switch with a `gray` active color // Always pass the $class to the mixin @include switch($color: gray, $size: 20px, $class: $class); } }
ここでは、最初に、$selector
をパラメーターとして受け取るget-switch-class
という名前のSass関数を作成します。 $selector
をSass関数のチェーンを介して実行し、ファーストクラス名を抽出しようとします。 たとえば、次を受け取った場合:
.class-1 .class-2, .class-3 .class-4
、class-1
を返します。.class-5.class-6 > .class-7.class-8
、class-5
を返します。
次に、.switch
クラスのスタイルを定義します。 トグルクラスを動的に.switch-toggle
に設定し、それを$toggle
変数に割り当てます。 get-switch-class()
関数呼び出しから返されたクラス名を$class
変数に割り当てることに注意してください。 最後に、テーマクラスを作成するために必要なパラメータとともにswitch
ミックスインを含めます。
テーマスイッチのセレクターの構造は次のようになっていることに注意してください:&#{$self}--default > #{$toggle}
(デフォルトのテーマを例として使用)。 すべてをまとめると、これは、スタイルを適用するために、要素の階層が次のようになる必要があることを意味します。
<!-- Use the default theme: switch--default --> <element class="switch switch--default"> <!-- The switch is in enabled state: switch-toggle--on --> <element class="switch-toggle switch-toggle--on"></element> </element>
これは、トグルスイッチのテーマがどのように見えるかを示すデモです。
ステップ4—サンプルアプリを構築する
ToggleSwitch
Reactコンポーネントに必要なスタイルを設定したので、先に進んで、チュートリアルの最初に見たサンプルアプリの作成を開始しましょう。
src/App.js
ファイルを次のコードスニペットのように変更します。
src / App.js
import classnames from 'classnames'; import snakeCase from 'lodash/snakeCase'; import React, { Component } from 'react'; import Switch from './components/ToggleSwitch'; import './App.css'; // List of activities that can trigger notifications const ACTIVITIES = [ 'News Feeds', 'Likes and Comments', 'Live Stream', 'Upcoming Events', 'Friend Requests', 'Nearby Friends', 'Birthdays', 'Account Sign-In' ]; class App extends Component { // Initialize app state, all activities are enabled by default state = { enabled: false, only: ACTIVITIES.map(snakeCase) } toggleNotifications = ({ enabled }) => { const { only } = this.state; this.setState({ enabled, only: enabled ? only : ACTIVITIES.map(snakeCase) }); } render() { const { enabled } = this.state; const headingClasses = classnames( 'font-weight-light h2 mb-0 pl-4', enabled ? 'text-dark' : 'text-secondary' ); return ( <div className="App position-absolute text-left d-flex justify-content-center align-items-start pt-5 h-100 w-100"> <div className="d-flex flex-wrap mt-5" style={{width: 600}}> <div className="d-flex p-4 border rounded align-items-center w-100"> <Switch theme="default" className="d-flex" enabled={enabled} onStateChanged={this.toggleNotifications} /> <span className={headingClasses}>Notifications</span> </div> {/* ... Notification options here ... */} </div> </div> ); } } export default App;
ここでは、通知をトリガーできる一連のアクティビティを使用してACTIVITIES
定数を初期化します。 次に、2つのプロパティを使用してアプリの状態を初期化しました。
enabled
:通知が有効かどうかを示すboolean
。only
:通知をトリガーするために有効になっているすべてのアクティビティを含むarray
。
LodashのsnakeCase
ユーティリティを使用して、状態を更新する前にアクティビティをスネークケースに変換しました。 したがって、'News Feeds'
は'news_feeds'
になります。
次に、通知スイッチから受信した状態に基づいてアプリの状態を更新するtoggleNotifications()
メソッドを定義しました。 これは、トグルスイッチのonStateChanged
プロップに渡されるコールバック関数として使用されます。 only
状態プロパティにはすべてのアクティビティが入力されているため、アプリを有効にすると、すべてのアクティビティがデフォルトで有効になることに注意してください。
最後に、アプリのDOM要素をレンダリングし、通知オプション用のスロットを残しました。これはまもなく追加されます。 この時点で、アプリは次のスクリーンショットのようになります。
次に、先に進んで、このコメントがある行を探します。
{/* ... Notification options here ... */}
通知オプションを表示するには、次のコンテンツに置き換えます。
src / App.js
// ... { enabled && ( <div className="w-100 mt-5"> <div className="container-fluid px-0"> <div className="pt-5"> <div className="d-flex justify-content-between align-items-center"> <span className="d-block font-weight-bold text-secondary small">Email Address</span> <span className="text-secondary small mb-1 d-block"> <small>Provide a valid email address with which to receive notifications.</small> </span> </div> <div className="mt-2"> <input type="text" placeholder="[email protected]" className="form-control" style={{ fontSize: 14 }} /> </div> </div> <div className="pt-5 mt-4"> <div className="d-flex justify-content-between align-items-center border-bottom pb-2"> <span className="d-block font-weight-bold text-secondary small">Filter Notifications</span> <span className="text-secondary small mb-1 d-block"> <small>Select the account activities for which to receive notifications.</small> </span> </div> <div className="mt-5"> <div className="row flex-column align-content-start" style={{ maxHeight: 180 }}> { this.renderNotifiableActivities() } </div> </div> </div> </div> </div> ) }
this.renderNotifiableActivities()
を呼び出してアクティビティをレンダリングしたことに気付くかもしれません。 先に進んで、このメソッドと他の残りのメソッドを実装しましょう。
App
コンポーネントに次のメソッドを追加します。
src / App.js
// ... class App extends Component { // ... toggleActivityEnabled = activity => ({ enabled }) => { let { only } = this.state; if (enabled && !only.includes(activity)) { only.push(activity); return this.setState({ only }); } if (!enabled && only.includes(activity)) { only = only.filter(item => item !== activity); return this.setState({ only }); } } renderNotifiableActivities() { const { only } = this.state; return ACTIVITIES.map((activity, index) => { const key = snakeCase(activity); const enabled = only.includes(key); const activityClasses = classnames( 'small mb-0 pl-3', enabled ? 'text-dark' : 'text-secondary' ); return ( <div key={index} className="col-5 d-flex mb-3"> <Switch theme="graphite-small" className="d-flex" enabled={enabled} onStateChanged={ this.toggleActivityEnabled(key) } /> <span className={activityClasses}>{ activity }</span> </div> ); }) } // ... }
ここでは、renderNotifiableActivities
メソッドを実装しました。 ACTIVITIES.map()
を使用してすべてのアクティビティを繰り返し、それぞれにトグルスイッチを使用してレンダリングします。 トグルスイッチがgraphite-small
テーマを使用していることに注意してください。 また、各アクティビティのenabled
状態は、only
状態変数にすでに存在するかどうかを確認することで検出されます。
最後に、各アクティビティのトグルスイッチのonStateChanged
小道具にコールバック関数を提供するために使用されるtoggleActivityEnabled
メソッドを定義しました。 アクティビティを引数として渡し、コールバック関数を返すことができるように、高階関数として定義しました。 アクティビティがすでに有効になっているかどうかを確認し、それに応じて状態を更新します。
これで、アプリは次のスクリーンショットのようになります。
最初のスクリーンショットに示されているように有効にするのではなく、デフォルトですべてのアクティビティを無効にしたい場合は、App
コンポーネントに次の変更を加えることができます。
[src/App.js] // ... class App extends Component { // Initialize app state, all activities are disabled by default state = { enabled: false, only: [] } toggleNotifications = ({ enabled }) => { const { only } = this.state; this.setState({ enabled, only: enabled ? only : [] }); } }
このステップで、トグルスイッチの作成が完了しました。 次のステップでは、アプリケーションへのアクセシビリティを改善する方法を学びます。
ステップ5—アクセシビリティの懸念に対処する
従来のチェックボックスの代わりにアプリケーションでトグルスイッチを使用すると、特に従来のチェックボックスを好きなようにスタイル設定するのが難しいため、よりすっきりとしたインターフェイスを作成できます。
ただし、チェックボックスの代わりにトグルスイッチを使用すると、ユーザーエージェントがコンポーネントの機能を正しく解釈できない可能性があるため、アクセシビリティの問題が発生します。
トグルスイッチのアクセシビリティを改善し、ユーザーエージェントが役割を正しく理解できるようにするために、いくつかのことができます。 たとえば、次のARIA属性を使用できます。
<switch-element tabindex="0" role="switch" aria-checked="true" aria-labelledby="#label-element"></switch-element>
トグルスイッチでさらに多くのイベントをリッスンして、ユーザーがコンポーネントを操作する方法を増やすこともできます。
結論
このチュートリアルでは、さまざまなテーマをサポートする適切なスタイルでReactアプリケーション用のカスタムトグルスイッチを作成しました。 従来のチェックボックスの代わりに、アプリケーションでそれを使用する方法を検討しました。 さらに、関連するアクセシビリティの懸念と、改善を行うために何ができるかを調査しました。
このチュートリアルの完全なソースコードについては、GitHubのreact-toggle-switch-demoリポジトリを確認してください。 また、CodeSandboxでこのチュートリアルのライブデモを入手することもできます。