1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[初心者向け]アニメーション付きトグルボタンの作り方を解説

Last updated at Posted at 2025-02-13

はじめに

アプリケーション開発において、必ずと言って過言ではないボタン。
CSS当ててリッチなデザインを作ろうってパターンは非常に多いと思います。
hover, activeなどの状態で色が変わるというのはよくあると思いますが、アニメーションもつけるとなると一手間かかってどうやるの?となりがち。

スタイルの当て方はいろいろあるのでこれが正解というわけではないのですが、スタイルの当て方を自分の勉強がてら初学者向けにまとめてみようと思います。

成果物

以下の様なスライド式ボタンを作成したいと思います。
初期状態はhogeが選択されていて、fugaをクリックすると背景色がアニメーションで移動して、ボタンの下のテキストが選択されたボタンに応じて変化します。
テキストが変化するのはレンダリングを切り替える様な挙動をイメージしています。

See the Pen base-button-sample7 by Yoshi2885 (@Yoshi2885) on CodePen.

地味に、クリックできる領域だけカーソルがポインターになる様なスタイルも当てています。

ベースを作成する

まずはスタイルが当たっていない状態のベースを作成したいと思います。

CodePenだと以下の通りです

See the Pen base-button-sample1 by Yoshi2885 (@Yoshi2885) on CodePen.

Reactの中身は、単純にボタンを2個配置したものと、Stateに応じて表示を切り替える部分を作成しました。
まだ、Styleは何も当てていない状態です。

CodePen見ればコード載せているので分かりますが、一応載せておきます。

const ChildComponent = () => {
  const [isHogeChecked, setIsHogeChecked] = useState<boolean>(true);
  return (
    <>
      <div>
        <button>hoge</button>
        <button>fuga</button>
      </div>
      {isHogeChecked ? (
        <div>hoge is selected</div>
      ) : (
        <div>fuga is selected</div>
      )}
    </>
  );
};

ボタンを押した時の挙動を追加する

次に、ボタンを押した時にStateを切り替えるためのonClickメソッドを作成します。

const ChildComponent = () => {
    const [isHogeChecked, setIsHogeChecked] = useState<boolean>(true);
  return (
    <>
      <div>
-       <button>hoge</button>
+       <button onClick={() => setIsHogeChecked(true)}>hoge</button>

-       <button>fuga</button>
+       <button onClick={() => setIsHogeChecked(false)}>hoge</button>
      </div>
      {isHogeChecked ? (
        <div>hoge is selected</div>
      ) : (
        <div>fuga is selected</div>
      )}
    </>
  );
};

これで、ボタンをクリックするとStateが切り替わり、

      {isHogeChecked ? (
        <div>hoge is selected</div>
      ) : (
        <div>fuga is selected</div>
      )}

この部分の表示が切り替わる様になります。
これで、Styleを当てる以外の要素は完成です。

ボタンの背景を作成する

まずは、2個のボタンをひとまとめにする背景を作成します。
二つのbuttonタグを囲っているdivタグにclassNameを付与します。

const ChildComponent = () => {
  const [isHogeChecked, setIsHogeChecked] = useState<boolean>(true);
  return (
    <>
-     <div>
+     <div className="toggleArea">
        <button onClick={() => setIsHogeChecked(true)}>hoge</button>
        <button onClick={() => setIsHogeChecked(false)}>hoge</button>
      </div>
      {isHogeChecked ? (
        <div>hoge is selected</div>
      ) : (
        <div>fuga is selected</div>
      )}
    </>
  );
};

サクッと背景をつけると以下の様なscssを当てます。
(*.module.scssを日頃使うのですが今回はCodePen仕様でStyleを当てます)

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  flex-wrap: wrap;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;
}

ここまでやるとグレー背景の中にボタンが並んだ状態になります。

See the Pen base-button-sample1 by Yoshi2885 (@Yoshi2885) on CodePen.

ボタンのスタイルを調整する

まずは、デフォルトのボタンのスタイルを消しながら、
グレー背景の中でいい感じの位置にテキストが表示される様に修正をしていきます。

hoge fuga ボタンのどちらも同じスタイルを当てていこうと思うので同じクラス名を与えます。

const ChildComponent = () => {
  const [isHogeChecked, setIsHogeChecked] = useState<boolean>(true);
  return (
    <>
      <div className="toggleArea">
-       <button onClick={() => setIsHogeChecked(true)}>hoge</button>
+       <button className="toggleButton" onClick={() => setIsHogeChecked(true)}>hoge</button>

-       <button onClick={() => setIsHogeChecked(false)}>hoge</button>
+       <button className="toggleButton" onClick={() => setIsHogeChecked(false)}>hoge</button>
      </div>
      {isHogeChecked ? (
        <div>hoge is selected</div>
      ) : (
        <div>fuga is selected</div>
      )}
    </>
  );
};

.toggleButton にスタイルを当てます。

やりたいことは、

  1. 全体を埋めるように横幅を調整
  2. defaultの色やボーダーを削除
  3. テキストのサイズを調整
  4. カーソルをポインターに変更

です。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  position: relative;
  align-items: center;
  align-content: center;
  flex-wrap: wrap;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;
}

+ .toggleButton {
+   flex: 1;
+   border: none;
+   font-size: 14px;
+   cursor: pointer;
+ }

ここまででこの様な形になります。

See the Pen base-button-sample1 by Yoshi2885 (@Yoshi2885) on CodePen.

スライドする白背景を作成する

現状ではグレー背景しかないのでどちらが選択されているか視認できません。
そのため、グレー背景の擬似要素として白背景のスライダーを作成します。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;

+ &::before {
+   content: "";
+   height: 24px;
+   width: 136px;
+   background-color: white;
+   border-radius: 4px;
+ }
}

.toggleButton {
  flex: 1;
  border: none;
  font-size: 14px;
  cursor: pointer;
}

擬似要素は、contentに空文字を入れないと表示されないので忘れない様にしてください。
height, widthで要素の大きさを指定し、background-colorで色を調整します。
border-radiusを背景のグレーと同じように4pxでつけて形を調整します。

このままでは、擬似要素はボタンの上に期待通りには表示されません。
背景のグレーとの位置調整が必要になるのでpositionを親要素と擬似要素にそれぞれつけます。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
+ position: relative;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;

  &::before {
    content: "";
+   position: absolute;
+   top: 4px;
+   left: 4px;
    height: 24px;
    width: 136px;
    background-color: white;
    border-radius: 4px;
  }
}

.toggleButton {
  flex: 1;
  border: none;
  font-size: 14px;
  cursor: pointer;
}

top, left の位置調整は、CodePen上では不要でしたが、ローカルでやってみるとズレたのでつけています。

ここまでの状態は以下の通りです。

See the Pen base-button-sample2 by Yoshi2885 (@Yoshi2885) on CodePen.

見てみると、擬似要素がボタンのテキストの上に来てしまい、hogeの文字が隠れてしまいました。
なので、z-indexを調整して、hogeを全面に出そうと思います。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  position: relative;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;

  &::before {
    content: "";
    position: absolute;
    top: 4px;
    left: 4px;
    height: 24px;
    width: 136px;
    background-color: white;
    border-radius: 4px;
  }
}

.toggleButton {
  flex: 1;
  border: none;
  font-size: 14px;
  cursor: pointer;
+ z-index: 1;
}

そうすると、ボタンの色に隠れて擬似要素の背景が見えなくなってしまったので、
ボタンの背景を透過しようと思います。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  position: relative;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;

  &::before {
    content: "";
    position: absolute;
    top: 4px;
    left: 4px;
    height: 24px;
    width: 136px;
    background-color: white;
    border-radius: 4px;
  }
}

.toggleButton {
  flex: 1;
  border: none;
  font-size: 14px;
+ background: transparent;
  cursor: pointer;
  z-index: 1;
}

これで背景はいい感じに見える様になりました。

See the Pen base-button-sample3 by Yoshi2885 (@Yoshi2885) on CodePen.

白背景を動かす

ボタンのクリックによって背景の表示位置を調整したいと思います。
今回は、isHogeCheckedのステートを使って切り替えを行いたいと思います。
まず、親要素のdivタグにdata-checkedでStateの状態を持たせます。

const ChildComponent = () => {
  const [isHogeChecked, setIsHogeChecked] = useState<boolean>(true);
  return (
    <>
-       <div className="toggleArea">
+       <div className="toggleArea" data-checked={isHogeChecked}>
        <button
          className="toggleButton"
          onClick={() => setIsHogeChecked(true)}
        >
          hoge
        </button>
        <button
          className="toggleButton"
          onClick={() => setIsHogeChecked(false)}
        >
          fuga
        </button>
      </div>
      {isHogeChecked ? (
        <div>hoge is selected</div>
      ) : (
        <div>fuga is selected</div>
      )}
    </>
  );
};

このdata-checkedを使って擬似要素の表示位置をtransform, translateXを使って変更します。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  position: relative;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;

  &::before {
    content: "";
    position: absolute;
    top: 4px;
    left: 4px;
    height: 24px;
    width: 136px;
    background-color: white;
    border-radius: 4px;
  }

+  &[data-checked='false']::before {
+    transform: translateX(136px);
+  }
}

.toggleButton {
  flex: 1;
  border: none;
  font-size: 14px;
  background: transparent;
  cursor: pointer;
  z-index: 1;
}

この状態まで来るとこの様な表示になります。
ほぼ完成している感じです👍

See the Pen base-button-sample5 by Yoshi2885 (@Yoshi2885) on CodePen.

動作にアニメーションをつける

白背景のスライダーが移動するようになったので、この動作にアニメーションをつけます。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  position: relative;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;

  &::before {
    content: "";
    position: absolute;
    top: 4px;
    left: 4px;
    height: 24px;
    width: 136px;
    background-color: white;
    border-radius: 4px;
+   transition: transform 0.3s ease;
  }

  &[data-checked='false']::before {
    transform: translateX(136px);
  }
}

.toggleButton {
  flex: 1;
  border: none;
  font-size: 14px;
  background: transparent;
  cursor: pointer;
  z-index: 1;
}

この1行だけ追加すればOKです。
アニメーションの動きの部分は、easeにしていますが、
ease-in-outなどもよく使われているのでいろいろ試してみてください。

動きの部分はcubic-bezierを使うと自作の動きを作ることも出来ます

See the Pen base-button-sample6 by Yoshi2885 (@Yoshi2885) on CodePen.

スタイルの微調整

動きとしては9割完了しているのですが、もう少しだけスタイルにこだわりたいと思います。

具体的には、

  • ポインターの調整
  • フォントカラーの調整

を行いたいと思います。
スライダーが選択されているところはボタンをクリックしても反応しないので、カーソルをポインターにしない様にするのと、非選択になっているボタンのテキストカラーをグレーよりに変更して選択されている方がより目立つ様にしたいと思います。

ボタンのどちらがアクティブになっているのかをisHogeCheckedのStateを使ってクラスを付与し、そこにスタイルを当てていく戦略を取りたいと思います。

const ChildComponent = () => {
  const [isHogeChecked, setIsHogeChecked] = useState<boolean>(true);
  return (
    <>
      <div className="toggleArea" data-checked={isHogeChecked}>
        <button
-         className="toggleButton"
+         className={`toggleButton ${isHogeChecked ? "active" : ""}`}
          onClick={() => setIsHogeChecked(true)}
        >
          hoge
        </button>
        <button
-         className="toggleButton"
+         className={`toggleButton ${!isHogeChecked ? "active" : ""}`}
          onClick={() => setIsHogeChecked(false)}
        >
          fuga
        </button>
      </div>
      {isHogeChecked ? (
        <div>hoge is selected</div>
      ) : (
        <div>fuga is selected</div>
      )}
    </>
  );
};

activeのクラスが付与されている状態が、白背景のスライダーがある状態なので、カーソルと文字色の調整を行います。

.toggleArea {
  background-color: #eaedf1;
  display: flex;
  position: relative;
  width: 272px;
  height: 24px;
  padding: 4px;
  border-radius: 4px;
  margin-top: 16px;
  margin-bottom: 16px;

  &::before {
    content: "";
    position: absolute;
    top: 4px;
    left: 4px;
    height: 24px;
    width: 136px;
    background-color: white;
    border-radius: 4px;
    transition: transform 0.3s ease;
  }

  &[data-checked="false"]::before {
    transform: translateX(136px);
  }
}

.toggleButton {
  flex: 1;
  border: none;
  font-size: 14px;
+ color: #8293ab;
  cursor: pointer;
  background: transparent;
  z-index: 1;

+  &.active {
+    color: #2a3c56;
+    cursor: default;
+  }
}

これが完成系になります。

See the Pen base-button-sample7 by Yoshi2885 (@Yoshi2885) on CodePen.

CodePen上では綺麗にスタイルが当たっていますが、
環境に応じて追加でスタイルの調整が必要な場合もあります。
その場合は、各自で調整をお願いします。

最後に

比較的シンプルなトグルのスイッチをアニメーション付きで作成してみました。
トグルのOn/Offに応じて背景を変化させたり、その他のStateを活用しながらより複雑なアニメーション(例えば、クリック→送信中→送信完了を連続的に遷移させるなど)を作れる様になると、CSSともっとお友達になれるかも知れません。

ネット上に、コピペOK!というスタイルがたくさん転がっていますが、自分でどうやって作ればいいんだろう?というのを実際にやってみるとかなりいい学びになるので是非色々試しに作ってみてください!

今回の記事で、手順をまとめてみましたが、これが絶対の正解ではないので、モダンな書き方とか、もっとこうやったらシンプルに記述できるとか可読性が高くなるとか…先輩方の知見をコメント欄でいただけたらありがたいです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?