本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100
ボタン

ボタンを押すとつぶれて弾む

デモ

操作:クリック。 「さわってみる」を押すと自動再生が止まり、マウスで動かせます。

仕様

用途
ボタン
動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

押した瞬間にボタンを横へ広げながら縦につぶし、離したらバネのように弾ませて元の大きさに戻します。変形の支点(transform-origin)をボタンの下端の中央に置くので、下の辺は動かずに上の辺だけが沈み、床に押しつけられたように見えます。

つぶすのは押した瞬間の 0.12 秒で、横 1.1 倍・縦 0.8 倍にします。横と縦の倍率を掛けた値を 0.88 ほどに保つと、中身が詰まったまま形だけ変わったように見えます。戻すのは 0.9 秒の elastic.out で、1 倍を行き過ぎては戻るので、つぶれる → 縦に伸びる → 落ち着く、と弾みます。

押したままカーソルをボタンの外へずらして離すと、ボタンには pointerup が届かず、つぶれたまま残ります。pointerleave でも戻すようにします。キーボードでは、Enter かスペースを押しているあいだつぶし、キーを離すか焦点が外れたら戻します。

厚みは、ぼかしの無い影を地の墨より暗い色で 4px 下にずらして描きます。影もボタンと一緒に縮むので、つぶれたときは厚みも薄くなり、押し込まれたように見えます。動きを減らす設定では、弾ませずに一瞬でつぶして戻すだけにします。

縦の倍率を 0.9 に近づけると控えめになり、elastic の 2 つ目の値(0.35)を大きくすると弾みがゆったりになります。カートに入れる・いいね・ゲーム風の画面のように、押した手応えを大きく見せたいボタンに向いています。

コード

HTML
<button class="squash-button" type="button">カートに入れる</button>

CSS
.squash-button {
  --face: #262626;    /* 地 */
  --on-face: #ffffff; /* 文字 */
  --base: #000000;    /* 下にのぞく厚み。地より暗くする */
  --depth: 4px;       /* 厚みの高さ */
  height: 48px;
  padding: 0 32px;
  border: 0;
  border-radius: 999px;
  background-color: var(--face);
  box-shadow: 0 var(--depth) 0 var(--base); /* ぼかさない影を下にずらして、厚みに見せる */
  color: var(--on-face);
  font: inherit;
  font-size: 15px; /* font: inherit の後に書く(先だと打ち消される) */
  font-weight: 600;
  letter-spacing: 0.04em;
  white-space: nowrap;
  cursor: pointer;
  user-select: none; /* 長押しで文字が選ばれないようにする */
  touch-action: manipulation; /* ダブルタップの拡大を待たずに押せる */
  -webkit-tap-highlight-color: transparent;
}

.squash-button:focus-visible {
  outline: 2px solid var(--face);
  outline-offset: 6px; /* 下の厚みと重ならない所に引く */
}

JavaScript
import { gsap } from "gsap";

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
// 動きを減らす設定では、弾ませずに一瞬でつぶして戻す
const time = (seconds) => (reduceMotion ? 0 : seconds);

document.querySelectorAll(".squash-button").forEach((button) => {
  // 下端を支点にすると、下の辺は動かず、床に押しつけられたようにつぶれる
  gsap.set(button, { transformOrigin: "50% 100%" });
  let pressed = false;

  // 横に広げて縦につぶす。横と縦の倍率を掛けて 0.88 ほどにすると、中身が詰まって見える
  const squash = () => {
    if (pressed) return;
    pressed = true;
    gsap.to(button, { scaleX: 1.1, scaleY: 0.8, duration: time(0.12), ease: "power2.out", overwrite: true });
  };

  // elastic が 1 を行き過ぎるので、縦に伸びてから落ち着く
  const release = () => {
    if (!pressed) return;
    pressed = false;
    gsap.to(button, { scaleX: 1, scaleY: 1, duration: time(0.9), ease: "elastic.out(1.1, 0.35)", overwrite: true });
  };

  button.addEventListener("pointerdown", (event) => {
    if (event.button === 0) squash(); // 右クリックではつぶさない
  });
  button.addEventListener("pointerup", release);
  // 押したまま外へ出て離すと pointerup が届かないので、出たときにも戻す
  button.addEventListener("pointerleave", release);
  button.addEventListener("pointercancel", release);

  // キーボードでは、Enter かスペースを押しているあいだつぶす
  const isPressKey = (event) => event.key === "Enter" || event.key === " ";
  button.addEventListener("keydown", (event) => {
    if (isPressKey(event) && !event.repeat) squash();
  });
  button.addEventListener("keyup", (event) => {
    if (isPressKey(event)) release();
  });
  button.addEventListener("blur", release);
});

インタラクションデザインについて、
お気軽にご相談ください。

ここに並べているのは、Web サイトの中で動く UI のサンプルです。新しく作るサイトに組み込むことも、いま公開中のサイトに、ボタンやヒーロー、スクロールの演出だけを足すこともできます。設計から実装までの進め方はWeb サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。