イージング比較デモ
linearからback・anticipateまで複数のtiming-functionを同時に走らせて違いを体感できます。モーション設計の学習に。
ライブデモ
使用例(お題: 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。