👈

React: Framer Motionを使ってめちゃBouncyなボタンを作ってみた!

2024/12/03に公開

この記事はコミューンプロダクト開発アドベントカレンダー2024のシリーズ2の3日目の記事です。

Reactのコンポーネントに簡単にアニメーションを実装するFramer Motionを使って、めっちゃ押せそうなボタンを作ってみた!

1. 準備

npm install motion

で、Framer Motionをインストール

とりあえずシンプルなボタンコンポーネントと

import React, { useState } from 'react';

import './button.css';

export const Button = () => {
  const [isChecked, setIsChecked] = useState(false);
    const handleClick = () => {
        setIsChecked(!isChecked);
    }

  const mode = isChecked ? 'primary' : 'secondary';
  const label = isChecked ? 'thanks :)' : 'click me!';

  return (
    <button
      type="button"
      className={['button', `button--${mode}`].join(' ')}
      onClick={handleClick}
    >
      {label}
    </button>
  );
};

cssを用意する

.button {
  position: relative;
  display: inline-block;
  cursor: pointer;
  border: 0;
  border-radius: 3em;
  font-weight: 700;
  padding: 11px 20px;
  font-size: 14px;  
}

.button--primary {
  background-color: #1ea7fd;
  color: white;
}
.button--secondary {
  background-color: #e6ebf1;
  color: #333;
}

2. 押し込む動き

次は、押し込む動きを実装してみる!
なるべくcssで済むとこはそちらにまとめて、motionでeasyになるとこをmotionに持たせる

css

.button:active {
  box-shadow: none;
}

.button--primary {
  ...
  box-shadow: 0 5px 0 #1687cd;
}

.button--secondary {
  ...
  box-shadow: 0 5px 0 #d1d8e0;
}

Button.tsx

import React, { useState } from 'react';
import { motion } from "framer-motion";

import './button.css';

export const Button = () => {
  const [isChecked, setIsChecked] = useState(false);
    const handleClick = () => {
        setIsChecked(!isChecked);
    }

  const mode = isChecked ? 'primary' : 'secondary';
  const label = isChecked ? 'thanks :)' : 'click me!';

  return (
    <motion.button
      whileTap={{  scale: 0.9, translateY: 5 }}
      type="button"
      className={['button', `button--${mode}`].join(' ')}
      onClick={handleClick}
    >
      {label}
    </motion.button>
  );
};
  • なんと、普通の<button>タグを、<motion.button>にするだけでmotionに必要なプロパティが使えるようになる
  • まずは試しにwhileTap。こちらはcssのactiveに相当する押し込んだ時の動き。おそらくライブラリ内部でtranslateをいじったりしているのでmotionにtranslateYを渡さずcssに渡すと上書きされて予期せぬ動きになってしまう。
    • 色や静的見た目系 -> css, 動き系 -> motionと分けると使いやすいかもしれない
  • 今回導入したscaleは、大きさを変えるプロパティ!なので、この場合押し込んだとき0.9倍の大きさになる。小さくなってBouncy!

今はこんな感じ!

押し込めてる雰囲気でいい感じですね!

3. よりBouncyに

<motion.button
  whileTap={{  scale: 0.9, translateY: 5 }}
  transition={{
    type: "spring",
    stiffness: 300,
    damping: 5,
  }}
  type="button"
  className={['button', `button--${mode}`].join(' ')}
  onClick={handleClick}
>

motion.buttonに、transitionを追加してみた。

  • transition: 動きに変化がある時にその動きの特徴を与えるプロパティ。
    • 色んなタイプがあるが、ここではバネのような動きをくれるSpring!
    • stiffnessは文字通り固さなので、値を大きくすると動きが激しく、小さくすると柔らかい動きになる
    • dampingは減衰。摩擦みたいなイメージ...。なんで大きくすると動きがすぐ止まったり、小さくすると緩やかな終わりになる、

良い感じでわwめっちゃバウンシーですね
これくらいがちょうど良い気がする。

4. もっともっとBouncy!(やりすぎ回)

<motion.button
  animate={{ scale: isChecked ? 1.2 : 1, }}
  whileTap={{  scale: 0.9, translateY: 5 }}
  transition={{
    type: "spring",
    stiffness: 300,
    damping: 5,
  }}
  type="button"
  className={['button', `button--${mode}`].join(' ')}
  onClick={handleClick}
>

motion.buttonに、animationも追加してみた。

  • animation: 位置や色を変化させたり、動かしたりと基本的なアニメーションを与えるプロパティ。
    • これを細かくtransitionで制御したりする。
  • ここでは、押した後に、大きくなって飛び出るようにしてみた。
    • isCheckedのときに1.2倍になる

Yeah... Bouncy...

5. まとめ

いかがだったでしょうか。前から押し込める雰囲気があるボタンは可愛いなあと思っていたので、その路線でよりインタラクティブなボタンが作れないか試行錯誤してみました。
3くらいで止めるとちょうど良いかもしれませんw
お読みいただきありがとうございました!

Discussion