イージング比較デモ

linearからback・anticipateまで複数のtiming-functionを同時に走らせて違いを体感できます。モーション設計の学習に。

#css#javascript#easing#transition

ライブデモ

使用例(お題: SaaS FlowDesk)

この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- FlowDesk:アニメーション設定でイージングを比較するパネル -->
<section class="ec-stage">
  <header class="ec-head">
    <div class="ec-brand"><span class="ec-mark">◆</span> FlowDesk</div>
    <p class="ec-title">モーション設定 · プレビュー</p>
  </header>

  <div class="ec-panel">
    <!-- 各行が異なる timing-function で同時に走る -->
    <div class="ec-track" data-ease="linear">
      <span class="ec-name">linear</span>
      <div class="ec-rail"><span class="ec-dot"></span></div>
    </div>
    <div class="ec-track" data-ease="ease-out">
      <span class="ec-name">ease-out</span>
      <div class="ec-rail"><span class="ec-dot"></span></div>
    </div>
    <div class="ec-track" data-ease="ease-in-out">
      <span class="ec-name">ease-in-out</span>
      <div class="ec-rail"><span class="ec-dot"></span></div>
    </div>
    <div class="ec-track" data-ease="anticipate">
      <span class="ec-name">anticipate</span>
      <div class="ec-rail"><span class="ec-dot"></span></div>
    </div>
    <div class="ec-track" data-ease="back">
      <span class="ec-name">back-out</span>
      <div class="ec-rail"><span class="ec-dot"></span></div>
    </div>
  </div>

  <div class="ec-foot">
    <span class="ec-hint">適用先:パネル展開トランジション</span>
    <button class="ec-btn" id="ecBtn" type="button">▶ 一斉プレビュー</button>
  </div>
</section>
CSS
/* FlowDesk:イージング比較プレビュー */
:root {
  --navy: #0f1b34;
  --blue: #4f7cff;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 15% 0%, #18294b 0%, #0f1b34 60%, #0b1428 100%);
  color: #eef2ff;
}

.ec-stage {
  height: 400px;
  padding: 18px 22px;
  display: flex;
  flex-direction: column;
}

.ec-head { margin-bottom: 14px; }
.ec-brand { font-size: 14px; font-weight: 800; letter-spacing: 0.04em; }
.ec-mark { color: var(--blue); }
.ec-title { margin: 4px 0 0; font-size: 12px; color: rgba(255, 255, 255, 0.6); }

.ec-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 14px;
  padding: 16px 16px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.ec-track { display: flex; align-items: center; gap: 12px; }
.ec-name {
  flex: none;
  width: 92px;
  font-size: 11px;
  font-variant: small-caps;
  letter-spacing: 0.03em;
  color: #9db4ff;
}

/* ドットが走る軌道 */
.ec-rail {
  position: relative;
  flex: 1;
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
}
.ec-dot {
  position: absolute;
  top: 50%;
  left: 0;
  width: 14px; height: 14px;
  margin-top: -7px;
  border-radius: 50%;
  background: linear-gradient(135deg, #6f97ff, var(--blue));
  box-shadow: 0 0 10px rgba(79, 124, 255, 0.7);
  transform: translateX(0);
}

/* 走行:JSで is-run を付与すると右端へ移動。各 timing-function は別々 */
.ec-track.is-run .ec-dot { transform: translateX(calc(100% - 14px)); }
.ec-track[data-ease="linear"] .ec-dot { transition: transform 1.1s linear; }
.ec-track[data-ease="ease-out"] .ec-dot { transition: transform 1.1s cubic-bezier(0, 0, 0.2, 1); }
.ec-track[data-ease="ease-in-out"] .ec-dot { transition: transform 1.1s cubic-bezier(0.65, 0, 0.35, 1); }
.ec-track[data-ease="anticipate"] .ec-dot { transition: transform 1.1s cubic-bezier(0.68, -0.55, 0.27, 1.05); }
.ec-track[data-ease="back"] .ec-dot { transition: transform 1.1s cubic-bezier(0.34, 1.56, 0.64, 1); }

.ec-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
}
.ec-hint { font-size: 11px; color: rgba(255, 255, 255, 0.5); }
.ec-btn {
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  padding: 9px 16px;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  color: #fff;
  background: linear-gradient(135deg, #5f8bff, var(--blue));
  box-shadow: 0 8px 18px rgba(79, 124, 255, 0.4);
  transition: transform 0.1s ease, box-shadow 0.2s ease;
}
.ec-btn:hover { box-shadow: 0 12px 24px rgba(79, 124, 255, 0.55); }
.ec-btn:active { transform: scale(0.97); }

@media (prefers-reduced-motion: reduce) {
  .ec-dot { transition: none !important; }
}
JavaScript
// FlowDesk:全トラックを一斉に走らせてイージングの差を見せる
(() => {
  const tracks = Array.from(document.querySelectorAll(".ec-track"));
  const btn = document.getElementById("ecBtn");
  if (!tracks.length) return; // null安全

  // 一度リセットしてから次フレームで走行開始
  const play = () => {
    tracks.forEach((t) => t.classList.remove("is-run"));
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        tracks.forEach((t) => t.classList.add("is-run"));
      });
    });
  };

  play(); // 初回プレビュー
  if (btn) btn.addEventListener("click", play);

  // 走り切ったら少し待って自動でループ
  setInterval(play, 2600);
})();

コード

HTML
<!-- イージング比較:複数のタイミング関数を同時に走らせて違いを体感 -->
<div class="ease-stage">
  <h2 class="ease-title">Easing Compare</h2>
  <div class="ease-rows" id="easeRows">
    <!-- 行は JS で生成(ラベル+トラック+ボール) -->
  </div>
  <button class="ease-run" id="easeRun" type="button">▶ 走らせる</button>
</div>
CSS
/* グリッド線のある暗い盤面でボールを走らせる */
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 360px;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Sans", "Yu Gothic UI", system-ui, sans-serif;
  background:
    linear-gradient(0deg, rgba(255,255,255,.03) 1px, transparent 1px) 0 0 / 100% 38px,
    radial-gradient(120% 120% at 50% 0%, #20233f 0%, #0d0e1c 70%);
  color: #eef0ff;
}
.ease-stage {
  width: min(420px, 90vw);
  padding: 18px 20px;
}
.ease-title {
  margin: 0 0 14px;
  font-size: 16px; letter-spacing: .04em;
  display: flex; align-items: center; gap: 8px;
}
.ease-title::before {
  content: ""; width: 12px; height: 12px; border-radius: 3px;
  background: linear-gradient(135deg, #ff7eb3, #39d3ff);
}

.ease-rows { display: grid; gap: 13px; }
.ease-row { display: grid; gap: 5px; }
.ease-name {
  font-size: 11px; letter-spacing: .03em;
  color: #aeb4e6; font-family: "Consolas", "SFMono-Regular", monospace;
}
/* ボールが走るトラック */
.ease-track {
  position: relative;
  height: 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.ease-ball {
  position: absolute;
  top: 50%;
  left: 4px;
  width: 14px; height: 14px;
  border-radius: 50%;
  transform: translate(0, -50%);
  background: radial-gradient(circle at 35% 30%, #fff, #7c8bff 60%, #4d5cff);
  box-shadow: 0 0 10px rgba(124, 139, 255, .8);
  /* duration は固定、timing-function は JS で個別指定 */
  transition: transform 1.4s;
}
.ease-row.run .ease-ball {
  /* トラック右端へ。--shift は JS で計算 */
  transform: translate(var(--shift, 360px), -50%);
}

.ease-run {
  margin-top: 16px; width: 100%;
  padding: 10px;
  border: 1px solid rgba(124, 139, 255, 0.4);
  border-radius: 10px;
  background: rgba(124, 139, 255, 0.2);
  color: #e7eaff; font-size: 13px; cursor: pointer;
  transition: background .2s ease, transform .1s ease;
}
.ease-run:hover { background: rgba(124, 139, 255, 0.34); }
.ease-run:active { transform: scale(.98); }

@media (prefers-reduced-motion: reduce) {
  .ease-ball { transition-duration: .01ms; }
}
JavaScript
// イージング比較:代表的な timing-function を行ごとに割り当てて同時再生
(() => {
  const rows = document.getElementById('easeRows');
  const runBtn = document.getElementById('easeRun');
  if (!rows) return; // null安全

  // 表示するイージング一覧
  const EASINGS = [
    { name: 'linear', fn: 'linear' },
    { name: 'ease-in-out', fn: 'ease-in-out' },
    { name: 'ease-out (cubic)', fn: 'cubic-bezier(.16,1,.3,1)' },
    { name: 'back (overshoot)', fn: 'cubic-bezier(.34,1.56,.64,1)' },
    { name: 'anticipate', fn: 'cubic-bezier(.68,-.55,.27,1.55)' },
  ];

  // 行を生成
  const ballEls = [];
  EASINGS.forEach((e) => {
    const row = document.createElement('div');
    row.className = 'ease-row';
    row.innerHTML =
      `<span class="ease-name">${e.name}</span>` +
      `<div class="ease-track"><span class="ease-ball"></span></div>`;
    const ball = row.querySelector('.ease-ball');
    ball.style.transitionTimingFunction = e.fn; // 個別イージング
    rows.appendChild(row);
    ballEls.push({ row, ball });
  });

  // トラック幅からゴール位置を算出してCSS変数に渡す
  const setShift = () => {
    ballEls.forEach(({ row, ball }) => {
      const track = row.querySelector('.ease-track');
      const shift = track.clientWidth - ball.offsetWidth - 8; // 端の余白ぶん
      ball.style.setProperty('--shift', shift + 'px');
    });
  };

  // リセット→次フレームで run を付与し再生
  const play = () => {
    setShift();
    ballEls.forEach(({ row }) => row.classList.remove('run'));
    // リフロー強制で transition を確実に再発火
    void rows.offsetWidth;
    requestAnimationFrame(() => {
      ballEls.forEach(({ row }) => row.classList.add('run'));
    });
  };

  window.addEventListener('resize', setShift);
  if (runBtn) runBtn.addEventListener('click', play);
  // 初期表示後に自動再生
  requestAnimationFrame(play);
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「イージング比較デモ」の効果を追加してください。

# 追加してほしい効果
イージング比較デモ(アニメーション & トランジション)
linearからback・anticipateまで複数のtiming-functionを同時に走らせて違いを体感できます。モーション設計の学習に。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- イージング比較:複数のタイミング関数を同時に走らせて違いを体感 -->
<div class="ease-stage">
  <h2 class="ease-title">Easing Compare</h2>
  <div class="ease-rows" id="easeRows">
    <!-- 行は JS で生成(ラベル+トラック+ボール) -->
  </div>
  <button class="ease-run" id="easeRun" type="button">▶ 走らせる</button>
</div>

【CSS】
/* グリッド線のある暗い盤面でボールを走らせる */
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 360px;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", "Hiragino Sans", "Yu Gothic UI", system-ui, sans-serif;
  background:
    linear-gradient(0deg, rgba(255,255,255,.03) 1px, transparent 1px) 0 0 / 100% 38px,
    radial-gradient(120% 120% at 50% 0%, #20233f 0%, #0d0e1c 70%);
  color: #eef0ff;
}
.ease-stage {
  width: min(420px, 90vw);
  padding: 18px 20px;
}
.ease-title {
  margin: 0 0 14px;
  font-size: 16px; letter-spacing: .04em;
  display: flex; align-items: center; gap: 8px;
}
.ease-title::before {
  content: ""; width: 12px; height: 12px; border-radius: 3px;
  background: linear-gradient(135deg, #ff7eb3, #39d3ff);
}

.ease-rows { display: grid; gap: 13px; }
.ease-row { display: grid; gap: 5px; }
.ease-name {
  font-size: 11px; letter-spacing: .03em;
  color: #aeb4e6; font-family: "Consolas", "SFMono-Regular", monospace;
}
/* ボールが走るトラック */
.ease-track {
  position: relative;
  height: 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.ease-ball {
  position: absolute;
  top: 50%;
  left: 4px;
  width: 14px; height: 14px;
  border-radius: 50%;
  transform: translate(0, -50%);
  background: radial-gradient(circle at 35% 30%, #fff, #7c8bff 60%, #4d5cff);
  box-shadow: 0 0 10px rgba(124, 139, 255, .8);
  /* duration は固定、timing-function は JS で個別指定 */
  transition: transform 1.4s;
}
.ease-row.run .ease-ball {
  /* トラック右端へ。--shift は JS で計算 */
  transform: translate(var(--shift, 360px), -50%);
}

.ease-run {
  margin-top: 16px; width: 100%;
  padding: 10px;
  border: 1px solid rgba(124, 139, 255, 0.4);
  border-radius: 10px;
  background: rgba(124, 139, 255, 0.2);
  color: #e7eaff; font-size: 13px; cursor: pointer;
  transition: background .2s ease, transform .1s ease;
}
.ease-run:hover { background: rgba(124, 139, 255, 0.34); }
.ease-run:active { transform: scale(.98); }

@media (prefers-reduced-motion: reduce) {
  .ease-ball { transition-duration: .01ms; }
}

【JavaScript】
// イージング比較:代表的な timing-function を行ごとに割り当てて同時再生
(() => {
  const rows = document.getElementById('easeRows');
  const runBtn = document.getElementById('easeRun');
  if (!rows) return; // null安全

  // 表示するイージング一覧
  const EASINGS = [
    { name: 'linear', fn: 'linear' },
    { name: 'ease-in-out', fn: 'ease-in-out' },
    { name: 'ease-out (cubic)', fn: 'cubic-bezier(.16,1,.3,1)' },
    { name: 'back (overshoot)', fn: 'cubic-bezier(.34,1.56,.64,1)' },
    { name: 'anticipate', fn: 'cubic-bezier(.68,-.55,.27,1.55)' },
  ];

  // 行を生成
  const ballEls = [];
  EASINGS.forEach((e) => {
    const row = document.createElement('div');
    row.className = 'ease-row';
    row.innerHTML =
      `<span class="ease-name">${e.name}</span>` +
      `<div class="ease-track"><span class="ease-ball"></span></div>`;
    const ball = row.querySelector('.ease-ball');
    ball.style.transitionTimingFunction = e.fn; // 個別イージング
    rows.appendChild(row);
    ballEls.push({ row, ball });
  });

  // トラック幅からゴール位置を算出してCSS変数に渡す
  const setShift = () => {
    ballEls.forEach(({ row, ball }) => {
      const track = row.querySelector('.ease-track');
      const shift = track.clientWidth - ball.offsetWidth - 8; // 端の余白ぶん
      ball.style.setProperty('--shift', shift + 'px');
    });
  };

  // リセット→次フレームで run を付与し再生
  const play = () => {
    setShift();
    ballEls.forEach(({ row }) => row.classList.remove('run'));
    // リフロー強制で transition を確実に再発火
    void rows.offsetWidth;
    requestAnimationFrame(() => {
      ballEls.forEach(({ row }) => row.classList.add('run'));
    });
  };

  window.addEventListener('resize', setShift);
  if (runBtn) runBtn.addEventListener('click', play);
  // 初期表示後に自動再生
  requestAnimationFrame(play);
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。