CSSアニメーションは、JavaScriptに頼らず手軽にWebサイトの操作感や視覚的魅力を向上させる必須スキルです。
本記事では、基本となるtransitionと@keyframesの使い分け、実務で使えるUIパーツや特殊エフェクトのコピペ用サンプル、JSを用いたスクロール連動を解説します。
また、CSSに関するカテゴリーページから学びたい内容を決めたい人は、以下のCSSページをご確認ください。
CSSアニメーションとは:2つの作り方
Web制作において、ボタンのホバーエフェクトや要素のフェードイン、スライドインといった動きはCSSのアニメーションとして実装するのがスタンダードです。
ここではCSSアニメーション入門として、土台となるCSSアニメーションの作り方の「2つのアプローチ」を解説します。
transitionとtransformで手軽に動かす@keyframesで複雑な動きを作る
transitionとtransformで手軽に動かす
CSSアニメーションの種類の中で、実務で多用されるのが「マウスを乗せた時(ホバー時)に動く」といったAからBへのシンプルな状態変化です。
これを作るには、変化にかかる時間を指定するtransitionプロパティ、要素の移動や拡大縮小を指定するtransformプロパティを組み合わせて使用します。
transitionプロパティは、「変化する前(ベースとなる元の要素)」に記述してください。
これにより、滑らかなアニメーションが保証されます。
下のボタンにマウスを乗せたり離したりしてください。
❌ 失敗:hover側にtransitionを書く
⭕️ 成功:ベース側にtransitionを書く
.btn-wrong {
transform: translateX(0);
}
.btn-wrong:hover {
transform: translateX(20px);
transition: 0.3s; /* ← マウスを離すと消える! */
}
/* ⭕️ ベースにかけておく */
.btn-correct {
transform: translateX(0);
transition: 0.3s ease; /* 行きも帰りも滑らかになる */
}
.btn-correct:hover {
transform: translateX(20px); /* 右へ20px移動 */
}
HTMLコード表示
<div class="anim-basic1-wrapper">
<div class="anim-basic1-demo-area">
<div class="anim-basic1-box">
<div class="anim-basic1-label">⏩ transitionとtransformによるアニメーション</div>
<div class="anim-basic1-visual">
<p style="font-size:12px; color:#555; margin-bottom:15px; text-align:center;">
下のボタンにマウスを乗せたり離したりしてください。
</p>
<div style="display:flex; flex-direction:column; gap:20px; align-items:center;">
<div style="text-align:center;">
<p style="font-size:12px; font-weight:bold; color:#dc3545; margin:0 0 5px 0;">❌ 失敗:hover側にtransitionを書く</p>
<button class="anim-basic1-btn-wrong">マウスを離すとワープします</button>
</div>
<div style="text-align:center;">
<p style="font-size:12px; font-weight:bold; color:#198754; margin:0 0 5px 0;">⭕️ 成功:ベース側にtransitionを書く</p>
<button class="anim-basic1-btn-correct">離した時も滑らかに戻ります</button>
</div>
</div>
</div>
<div class="anim-basic1-code">
/* ❌ 初心者のミス:ホバー時にだけかけてしまう */<br>
<span class="anim-basic1-hl-blue">.btn-wrong</span> {<br>
<span class="anim-basic1-hl-green">transform: translateX(0);</span><br>
}<br>
<span class="anim-basic1-hl-blue">.btn-wrong:hover</span> {<br>
<span class="anim-basic1-hl-green">transform: translateX(20px);</span><br>
<span class="anim-basic1-hl-red">transition: 0.3s;</span> /* ← マウスを離すと消える! */<br>
}<br><br>
/* ⭕️ ベースにかけておく */<br>
<span class="anim-basic1-hl-blue">.btn-correct</span> {<br>
<span class="anim-basic1-hl-green">transform: translateX(0);</span><br>
<span class="anim-basic1-hl-red">transition: 0.3s ease;</span> /* 行きも帰りも滑らかになる */<br>
}<br>
<span class="anim-basic1-hl-blue">.btn-correct:hover</span> {<br>
<span class="anim-basic1-hl-green">transform: translateX(20px);</span> /* 右へ20px移動 */<br>
}
</div>
</div>
</div>
</div>CSSコード表示
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.anim-basic1-demo-area {
display: flex;
justify-content: center;
}
.anim-basic1-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 550px;
border-radius: 4px;
}
.anim-basic1-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.anim-basic1-visual {
background-color: #f1f3f5;
padding: 30px 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
/* 共通のボタンスタイル */
.anim-basic1-btn-wrong, .anim-basic1-btn-correct {
background-color: #0d6efd;
color: #fff;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
font-size: 14px;
}
/* ❌ 失敗例:hover側にtransition */
.anim-basic1-btn-wrong {
transform: translateX(0);
}
.anim-basic1-btn-wrong:hover {
background-color: #0a58ca;
transform: translateX(30px);
/* ホバーした時だけtransitionが効く */
transition: all 0.5s ease;
}
/* ⭕️ 成功例:ベース側にtransition */
.anim-basic1-btn-correct {
transform: translateX(0);
/* ベースにtransitionがあるため、ホバー解除時も0.5sかけて戻る */
transition: all 0.5s ease;
}
.anim-basic1-btn-correct:hover {
background-color: #0a58ca;
transform: translateX(30px);
}
.anim-basic1-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #198754;
}
.anim-basic1-hl-green { color: #98c379; font-weight: bold; }
.anim-basic1-hl-blue { color: #61afef; font-weight: bold; }
.anim-basic1-hl-red { color: #e06c75; font-weight: bold; }transitionやtransformの使い方を詳しく知りたい人は以下から一読ください。
@keyframesで複雑な動きを作る
transitionが「AからBへの1回の変化」だとすれば、「A→B→C→Dと複数の状態を連続して変化させる」あるいは「自動でループさせ続ける」といった複雑な動きを実現するのが「keyframes」です。
ローディングのアニメーションやふわふわと浮遊するキャラクターなどはこの方法で作られます。
アニメーションが最終地点に到達した後、状態を維持してピタッと止めたい場合はanimationプロパティにforwards というキーワードを追加してください。
@keyframes slideRight {
0% { transform: translateX(0); }
100% { transform: translateX(200px); }
}
/* ❌ 失敗:再生後、強制的に0%の状態に戻される */
.box-wrong {
animation-name: slideRight;
animation-duration: 2s;
}
/* ⭕️ 正解:forwards をつけると100%の状態で止まる */
.box-correct {
animation-name: slideRight;
animation-duration: 2s;
animation-fill-mode: forwards; /* ←超重要! */
}
HTMLコード表示
<div class="anim-basic2-wrapper">
<div class="anim-basic2-demo-area">
<div class="anim-basic2-box">
<div class="anim-basic2-label">🎞 @keyframes と forwards の重要性</div>
<div class="anim-basic2-visual">
<div style="text-align:center; margin-bottom:15px;">
<button id="anim-basic2-btn-play" class="anim-basic2-play-btn">▶ アニメーション再生</button>
</div>
<div style="margin-bottom:20px; overflow:hidden; padding:10px; border:1px solid #ced4da; background:#fff;">
<p style="font-size:12px; font-weight:bold; color:#dc3545; margin:0 0 5px 0;">❌ forwardsなし(終わると最初に戻る)</p>
<div id="anim-basic2-item-wrong" class="anim-basic2-item anim-basic2-bg-red">BOX</div>
</div>
<div style="overflow:hidden; padding:10px; border:1px solid #ced4da; background:#fff;">
<p style="font-size:12px; font-weight:bold; color:#198754; margin:0 0 5px 0;">⭕️ forwardsあり(終わった位置で止まる)</p>
<div id="anim-basic2-item-correct" class="anim-basic2-item anim-basic2-bg-green">BOX</div>
</div>
</div>
<div class="anim-basic2-code">
/* 💡 アニメーションの設計図(右へ移動) */<br>
<span class="anim-basic2-hl-blue">@keyframes slideRight</span> {<br>
<span class="anim-basic2-hl-blue">0%</span> { <span class="anim-basic2-hl-green">transform: translateX(0);</span> }<br>
<span class="anim-basic2-hl-blue">100%</span> { <span class="anim-basic2-hl-green">transform: translateX(200px);</span> }<br>
}<br><br>
/* ❌ 失敗:再生後、強制的に0%の状態に戻される */<br>
<span class="anim-basic2-hl-blue">.box-wrong</span> {<br>
<span class="anim-basic2-hl-green">animation-name: slideRight;</span><br>
<span class="anim-basic2-hl-green">animation-duration: 2s;</span><br>
}<br><br>
/* ⭕️ 正解:forwards をつけると100%の状態で止まる */<br>
<span class="anim-basic2-hl-blue">.box-correct</span> {<br>
<span class="anim-basic2-hl-green">animation-name: slideRight;</span><br>
<span class="anim-basic2-hl-green">animation-duration: 2s;</span><br>
<span class="anim-basic2-hl-red">animation-fill-mode: forwards;</span> /* ←超重要! */<br>
}
</div>
</div>
</div>
</div>CSSコード表示
.anim-basic2-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.anim-basic2-demo-area {
display: flex;
justify-content: center;
}
.anim-basic2-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 550px;
border-radius: 4px;
}
.anim-basic2-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.anim-basic2-visual {
background-color: #f1f3f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
.anim-basic2-play-btn {
background-color: #333;
color: #fff;
border: none;
padding: 8px 20px;
border-radius: 50px;
cursor: pointer;
font-weight: bold;
}
/* アニメーション要素の共通設定 */
.anim-basic2-item {
width: 60px;
height: 40px;
color: #fff;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.anim-basic2-bg-red { background-color: #dc3545; }
.anim-basic2-bg-green { background-color: #198754; }
/* キーフレーム設計図 */
@keyframes slideRightDemo {
0% { transform: translateX(0); }
100% { transform: translateX(200px); }
}
/* JavaScriptで付与する動的クラス */
.anim-basic2-play-wrong {
animation-name: slideRightDemo;
animation-duration: 2s;
animation-timing-function: ease;
/* forwardsがないため戻る */
}
.anim-basic2-play-correct {
animation-name: slideRightDemo;
animation-duration: 2s;
animation-timing-function: ease;
/* 終わった位置で停止する */
animation-fill-mode: forwards;
}
.anim-basic2-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.anim-basic2-hl-green { color: #98c379; font-weight: bold; }
.anim-basic2-hl-blue { color: #61afef; font-weight: bold; }
.anim-basic2-hl-red { color: #e06c75; font-weight: bold; }JavaScriptコード表示
setTimeout(() => {
const btnPlay = document.getElementById('anim-basic2-btn-play');
const itemWrong = document.getElementById('anim-basic2-item-wrong');
const itemCorrect = document.getElementById('anim-basic2-item-correct');
if(btnPlay && itemWrong && itemCorrect) {
btnPlay.addEventListener('click', () => {
// 一度クラスを外してリセット
itemWrong.classList.remove('anim-basic2-play-wrong');
itemCorrect.classList.remove('anim-basic2-play-correct');
// ブラウザの再描画を強制するテクニック
void itemWrong.offsetWidth;
void itemCorrect.offsetWidth;
// クラスを付与してアニメーション開始
itemWrong.classList.add('anim-basic2-play-wrong');
itemCorrect.classList.add('anim-basic2-play-correct');
});
}
}, 100);コピペで使える!CSSアニメーションの動きサンプル集
ここでは、実務で使用されるCSSアニメーションのパターンを厳選し、コピペで使えるCSSアニメーションサンプル集としてまとめました。
ご自身のサイトのテイストに合わせて自由に調整してご活用ください。
- ふわふわ・ぽよん・ゆらゆら等のかわいい動き
- フェードイン・スライド・ズーム・回転などの定番
- 波紋・光る・点滅・雪などの特殊エフェクト
ふわふわ・ぽよん・ゆらゆら等のかわいい動き
要素がふわふわと宙に浮く動き、風にゆらゆらと揺れる動きは、女性向け・子供向けサイトで鉄板の演出です。
また、ボタンがぼよよんと跳ねる動きは、ユーザーのクリックを促すマイクロインタラクションとして有効です。
逆に、エラー時の警告としてカタカタと震わせる表現もあります。
例えば、上部を軸にして揺らしたい場合はtransform-origin: top center;(変形の起点を上部中央にする)を指定してください。
アニメーションは「どこを軸に動かすか」で表現が変わります。
また、これらのループアニメーションを多用しすぎると、画面全体が動きすぎてユーザーが「画面酔い」を起こすため、ワンポイントでの使用に留めるのがよいです。
ふわふわ(浮遊)
ゆらゆら(振り子)
ぽよん(バウンス)
@keyframes fuwafuwa {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-15px); }
}
.fuwafuwa {
animation: fuwafuwa 3s ease-in-out infinite;
}
/* 🔔 ゆらゆら(上部を軸にした回転ループ) */
@keyframes yurayura {
0%, 100% { transform: rotate(-10deg); }
50% { transform: rotate(10deg); }
}
.yurayura {
transform-origin: top center; /* 💡 軸を上に! */
animation: yurayura 2.5s ease-in-out infinite;
}
HTMLコード表示
<div class="anim-samp1-wrapper">
<div class="anim-samp1-demo-area">
<div class="anim-basic1-box">
<div class="anim-samp1-label">🎈 かわいいループアニメーション</div>
<div class="anim-samp1-visual">
<div class="anim-samp1-item-wrap">
<p class="anim-samp1-title">ふわふわ(浮遊)</p>
<div class="anim-samp1-target anim-samp1-fuwafuwa">☁️</div>
</div>
<div class="anim-samp1-item-wrap">
<p class="anim-samp1-title">ゆらゆら(振り子)</p>
<div class="anim-samp1-target anim-samp1-yurayura">🔔</div>
</div>
<div class="anim-samp1-item-wrap">
<p class="anim-samp1-title">ぽよん(バウンス)</p>
<div class="anim-samp1-target anim-samp1-poyon">🍮</div>
</div>
</div>
<div class="anim-samp1-code">
/* ☁️ ふわふわ(上下移動のループ) */<br>
<span class="anim-samp1-hl-blue">@keyframes fuwafuwa</span> {<br>
<span class="anim-samp1-hl-blue">0%, 100%</span> { <span class="anim-samp1-hl-green">transform: translateY(0);</span> }<br>
<span class="anim-samp1-hl-blue">50%</span> { <span class="anim-samp1-hl-green">transform: translateY(-15px);</span> }<br>
}<br>
<span class="anim-samp1-hl-blue">.fuwafuwa</span> {<br>
<span class="anim-samp1-hl-green">animation: fuwafuwa 3s ease-in-out infinite;</span><br>
}<br><br>
/* 🔔 ゆらゆら(上部を軸にした回転ループ) */<br>
<span class="anim-samp1-hl-blue">@keyframes yurayura</span> {<br>
<span class="anim-samp1-hl-blue">0%, 100%</span> { <span class="anim-samp1-hl-green">transform: rotate(-10deg);</span> }<br>
<span class="anim-samp1-hl-blue">50%</span> { <span class="anim-samp1-hl-green">transform: rotate(10deg);</span> }<br>
}<br>
<span class="anim-samp1-hl-blue">.yurayura</span> {<br>
<span class="anim-samp1-hl-red">transform-origin: top center;</span> /* 💡 軸を上に! */<br>
<span class="anim-samp1-hl-green">animation: yurayura 2.5s ease-in-out infinite;</span><br>
}
</div>
</div>
</div>
</div>CSSコード表示
.anim-samp1-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.anim-samp1-demo-area {
display: flex;
justify-content: center;
}
.anim-basic1-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 650px;
border-radius: 4px;
}
.anim-samp1-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.anim-samp1-visual {
background-color: #f1f3f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 20px;
}
.anim-samp1-item-wrap {
text-align: center;
width: 120px;
}
.anim-samp1-title {
font-size: 12px;
font-weight: bold;
color: #495057;
margin-bottom: 15px;
}
.anim-samp1-target {
font-size: 50px;
line-height: 1;
display: inline-block; /* transformを効かせるため必須 */
}
/* ☁️ ふわふわ */
@keyframes animSamp1Fuwafuwa {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-15px);
}
}
.anim-samp1-fuwafuwa {
animation: animSamp1Fuwafuwa 3s ease-in-out infinite;
}
/* 🔔 ゆらゆら */
@keyframes animSamp1Yurayura {
0%, 100% {
transform: rotate(-15deg);
}
50% {
transform: rotate(15deg);
}
}
.anim-samp1-yurayura {
transform-origin: top center;
animation: animSamp1Yurayura 2.5s ease-in-out infinite;
}
/* 🍮 ぽよん */
@keyframes animSamp1Poyon {
0%, 100% {
transform: scale(1, 1);
}
30% {
transform: scale(1.15, 0.85); /* 潰れる */
}
50% {
transform: scale(0.9, 1.1); /* 伸びる */
}
70% {
transform: scale(1.05, 0.95);
}
}
.anim-samp1-poyon {
transform-origin: bottom center;
animation: animSamp1Poyon 2s linear infinite;
}
.anim-samp1-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.anim-samp1-hl-green { color: #98c379; font-weight: bold; }
.anim-samp1-hl-blue { color: #61afef; font-weight: bold; }
.anim-samp1-hl-red { color: #e06c75; font-weight: bold; }フェードイン・スライド・ズーム・回転などの定番
スクロールして要素が見えた瞬間にフェードインさせる動きは、Webサイトで必須の実装です。
単なる「ふわっと」に加え、スライドインを組み合わせて「下から上」や「左から右」に移動させながら表示すると、洗練された印象になります。
また、写真のホバー時にズームインさせたり、アイコンを回転させたり、要素を消すフェードアウトも定番のUIアニメーションです。
例えば、スライドインアニメーションを実装する際は、大枠のコンテナにoverflow-x: hidden;を指定し、画面外にはみ出ている要素を隠し、横スクロールを防いでください。
Fade Up
(下から上へ)3D Rotate
(Y軸回転)@keyframes fadeUp {
0% {
opacity: 0;
transform: translateY(30px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.fade-up-element {
animation: fadeUp 0.8s ease forwards;
}
HTMLコード表示
<div class="anim-samp2-wrapper">
<div class="anim-samp2-demo-area">
<div class="anim-basic1-box">
<div class="anim-samp2-label">✨ 定番の登場・ホバーアニメーション</div>
<div class="anim-samp2-visual">
<div style="text-align:center; margin-bottom:15px;">
<button id="anim-samp2-trigger" class="anim-samp2-trigger-btn">↻ アニメーションを再生</button>
</div>
<div class="anim-samp2-grid">
<div class="anim-samp2-card">
<div class="anim-samp2-target anim-samp2-fade-up">
<p>Fade Up</p>
<span style="font-size:11px;">(下から上へ)</span>
</div>
</div>
<div class="anim-samp2-card">
<div class="anim-samp2-target anim-samp2-slide-right">
<p>Slide Right</p>
<span style="font-size:11px;">(左から右へ)</span>
</div>
</div>
<div class="anim-samp2-card">
<div class="anim-samp2-target anim-samp2-rotate-3d">
<p>3D Rotate</p>
<span style="font-size:11px;">(Y軸回転)</span>
</div>
</div>
</div>
</div>
<div class="anim-samp2-code">
/* 💡 定番:下から上へふわっとフェードイン */<br>
<span class="anim-samp2-hl-blue">@keyframes fadeUp</span> {<br>
<span class="anim-samp2-hl-blue">0%</span> {<br>
<span class="anim-samp2-hl-green">opacity: 0;</span><br>
<span class="anim-samp2-hl-green">transform: translateY(30px);</span><br>
}<br>
<span class="anim-samp2-hl-blue">100%</span> {<br>
<span class="anim-samp2-hl-green">opacity: 1;</span><br>
<span class="anim-samp2-hl-green">transform: translateY(0);</span><br>
}<br>
}<br>
<span class="anim-samp2-hl-blue">.fade-up-element</span> {<br>
<span class="anim-samp2-hl-green">animation: fadeUp 0.8s ease forwards;</span><br>
}
</div>
</div>
</div>
</div>CSSコード表示
.anim-samp2-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.anim-samp2-demo-area {
display: flex;
justify-content: center;
}
.anim-samp2-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.anim-samp2-visual {
background-color: #f1f3f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
overflow: hidden; /* ★横揺れ防止の鉄則 */
}
.anim-samp2-trigger-btn {
background-color: #333;
color: #fff;
border: none;
padding: 8px 20px;
border-radius: 50px;
cursor: pointer;
font-weight: bold;
}
.anim-samp2-grid {
display: flex;
justify-content: space-around;
gap: 15px;
}
.anim-samp2-card {
flex: 1;
height: 100px;
background-color: #fff;
border: 1px dashed #adb5bd;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.anim-samp2-target {
background-color: #0d6efd;
color: #fff;
width: 90%;
height: 80%;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
text-align: center;
}
.anim-samp2-target p { margin: 0; }
/* フェードアップ(下から上) */
@keyframes animSamp2FadeUp {
0% {
opacity: 0;
transform: translateY(30px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.anim-samp2-fade-up.is-active {
animation: animSamp2FadeUp 0.8s ease forwards;
}
/* スライドライト(左から右) */
@keyframes animSamp2SlideRight {
0% {
opacity: 0;
transform: translateX(-50px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
.anim-samp2-slide-right.is-active {
animation: animSamp2SlideRight 0.8s ease forwards;
}
/* 3D回転ループ */
@keyframes animSamp2Rotate3D {
0% {
transform: perspective(400px) rotateY(0deg);
}
100% {
transform: perspective(400px) rotateY(360deg);
}
}
.anim-samp2-rotate-3d.is-active {
animation: animSamp2Rotate3D 3s linear infinite;
}
.anim-samp2-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.anim-samp2-hl-green { color: #98c379; font-weight: bold; }
.anim-samp2-hl-blue { color: #61afef; font-weight: bold; }JavaScriptコード表示
setTimeout(() => {
const btn = document.getElementById('anim-samp2-trigger');
const targets = document.querySelectorAll('.anim-samp2-target');
function playAnimations() {
targets.forEach(target => {
target.classList.remove('is-active');
void target.offsetWidth; // リフロー強制
target.classList.add('is-active');
});
}
if(btn) {
btn.addEventListener('click', playAnimations);
// 初期表示時にも再生
playAnimations();
}
}, 100);はみだしを隠すプロパティであるoverflowの使い方を詳しく知りたい人は「【html&css】overflowの使い方とhiddenやscrollの使い分け」を一読ください。
波紋・光る・点滅・雪などの特殊エフェクト
ユーザーの目を惹きつけたい場合、ボタンをクリックした際に波紋が広がるエフェクト、テキストが光るエフェクト、ボタンに光沢が走るエフェクトが効果的です。
また、季節感を出すために背景に雪や流れ星を降らせたり、背景を波のように変形させたり、ホバーで要素が飛び出す表現もCSSのみで実装可能です。
例えば、波紋や光沢などのエフェクトは可能な限りbox-shadowのアニメーションを避け、疑似要素(::after)のtransform: scale()とopacityを組み合わせて表現してください。
transformとopacityはGPUで処理されるため、スマホでも負荷なく動きます。
波紋(Ripple)
光沢(Shine)
点滅(Glow)
SALE NOW.btn-ripple::after {
content: “”;
position: absolute;
inset: 0;
border: 2px solid #0d6efd;
border-radius: 50px;
animation: rippleAnim 1.5s infinite;
}
@keyframes rippleAnim {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.5); /* 💡 大きく広げて */
opacity: 0; /* 💡 消えさせる */
}
}
HTMLコード表示
<div class="anim-samp3-wrapper">
<div class="anim-samp3-demo-area">
<div class="anim-basic1-box">
<div class="anim-samp3-label">🪄 波紋・光沢・発光エフェクト</div>
<div class="anim-samp3-visual">
<div style="display:flex; justify-content:space-around; align-items:center; flex-wrap:wrap; gap:20px;">
<div style="text-align:center;">
<p class="anim-samp3-title">波紋(Ripple)</p>
<button class="anim-samp3-btn anim-samp3-ripple">CLICK ME</button>
</div>
<div style="text-align:center;">
<p class="anim-samp3-title">光沢(Shine)</p>
<button class="anim-samp3-btn anim-samp3-shine">HOVER ME</button>
</div>
<div style="text-align:center; background:#111; padding:15px; border-radius:4px;">
<p class="anim-samp3-title" style="color:#aaa;">点滅(Glow)</p>
<span class="anim-samp3-glow">SALE NOW</span>
</div>
</div>
</div>
<div class="anim-samp3-code">
/* 🌊 波紋エフェクト(ボタンの疑似要素を拡大・透過させる) */<br>
<span class="anim-samp3-hl-blue">.btn-ripple::after</span> {<br>
<span class="anim-samp3-hl-green">content: "";</span><br>
<span class="anim-samp3-hl-green">position: absolute;</span><br>
<span class="anim-samp3-hl-green">inset: 0;</span><br>
<span class="anim-samp3-hl-green">border: 2px solid #0d6efd;</span><br>
<span class="anim-samp3-hl-green">border-radius: 50px;</span><br>
<span class="anim-samp3-hl-green">animation: rippleAnim 1.5s infinite;</span><br>
}<br>
<span class="anim-samp3-hl-blue">@keyframes rippleAnim</span> {<br>
<span class="anim-samp3-hl-blue">0%</span> {<br>
<span class="anim-samp3-hl-green">transform: scale(1);</span><br>
<span class="anim-samp3-hl-green">opacity: 1;</span><br>
}<br>
<span class="anim-samp3-hl-blue">100%</span> {<br>
<span class="anim-samp3-hl-red">transform: scale(1.5);</span> /* 💡 大きく広げて */<br>
<span class="anim-samp3-hl-red">opacity: 0;</span> /* 💡 消えさせる */<br>
}<br>
}
</div>
</div>
</div>
</div>CSSコード表示
.anim-samp3-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.anim-samp3-demo-area {
display: flex;
justify-content: center;
}
.anim-samp3-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.anim-samp3-visual {
background-color: #f1f3f5;
padding: 30px 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
.anim-samp3-title {
font-size: 12px;
font-weight: bold;
color: #495057;
margin-bottom: 15px;
}
.anim-samp3-btn {
background-color: #0d6efd;
color: #fff;
border: none;
padding: 12px 25px;
border-radius: 50px;
cursor: pointer;
font-weight: bold;
position: relative;
/* はみ出し制御や基準点の設定 */
}
/* 🌊 波紋エフェクト */
.anim-samp3-ripple {
/* 波紋が親を基準に広がるように */
}
.anim-samp3-ripple::after {
content: "";
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
border: 2px solid #0d6efd;
border-radius: 50px;
animation: animSamp3Ripple 1.5s ease-out infinite;
}
@keyframes animSamp3Ripple {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.4, 1.6); /* 横・縦に広がる */
opacity: 0;
}
}
/* ✨ 光沢(キラキラ)エフェクト */
.anim-samp3-shine {
overflow: hidden; /* 光が外にはみ出ないように */
}
.anim-samp3-shine::before {
content: "";
position: absolute;
top: 0;
left: -100%; /* 左の外側に待機 */
width: 50%;
height: 100%;
/* 斜めの白いグラデーション(光) */
background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0) 100%);
transform: skewX(-20deg); /* 斜めにする */
transition: all 0.5s;
}
.anim-samp3-shine:hover::before {
left: 150%; /* ホバー時に右端まで駆け抜ける */
}
/* 🚨 発光・点滅(テキスト) */
.anim-samp3-glow {
color: #ffeb3b;
font-size: 18px;
font-weight: 900;
letter-spacing: 2px;
animation: animSamp3Glow 1.5s ease-in-out infinite alternate;
}
@keyframes animSamp3Glow {
0% {
opacity: 0.5;
text-shadow: 0 0 5px rgba(255, 235, 59, 0.2);
}
100% {
opacity: 1;
text-shadow: 0 0 10px rgba(255, 235, 59, 0.8), 0 0 20px rgba(255, 235, 59, 0.6);
}
}
.anim-samp3-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #dc3545;
}
.anim-samp3-hl-green { color: #98c379; font-weight: bold; }
.anim-samp3-hl-blue { color: #61afef; font-weight: bold; }
.anim-samp3-hl-red { color: #e06c75; font-weight: bold; }疑似要素である::beforeや::afterの使い方を詳しく知りたい人は「【CSS】::before・::after擬似要素の使い方:画像・アイコン・ホバーエフェクト」を一読ください。
パーツ別:ボタン・文字・画像のアニメーション実装
Webサイトを構成する個々のパーツ動きを加えることで、ユーザー体験(UX)は向上します。
ここでは、実務の現場で実装を求められる「ボタン」「テキスト」「画像や装飾線」「UIパーツ」にフォーカスし、コピペして使える実践的なコードを解説します。
- ボタンや矢印のホバーアニメーション
- テキスト(一文字ずつ表示・グラデーション)の演出
- 画像・背景・線が伸びるアニメーション
- ハンバーガーメニュー・アコーディオン等のUIパーツ
ボタンや矢印のホバーアニメーション
ユーザーにクリックを促すボタンは、サイトのコンバージョンに直結する重要パーツです。
「おしゃれ」かつ「シンプル」な実装が主流です。(むずすぎる)
よく使われるホバーアニメーションとして、クリックした感覚を与えるへこむ動き、高級感を演出する光る演出があります。
また、リンクであることを強調する矢印では、ホバー時に伸びるといった細かなインタラクションが好まれます。
要素の移動には、transform: translateX();を使用してください。
周りのレイアウトに影響を与えず滑らかに動きます。
.btn-dent {
box-shadow: 0 6px 0 #0a58ca;
transition: all 0.2s ease;
}
.btn-dent:active { /* ホバーではなくクリック時(:active)がおすすめ */
transform: translateY(4px); /* 下へ移動 */
box-shadow: 0 2px 0 #0a58ca; /* 影を短くする */
}
/* ➡️ 矢印が伸びる(translateXで移動) */
.btn-arrow .arrow {
display: inline-block;
transition: transform 0.3s ease;
}
.btn-arrow:hover .arrow {
transform: translateX(10px); /* 右へ10px移動 */
}
HTMLコード表示
<div class="pt-sec1-wrapper">
<div class="pt-sec1-demo-area">
<div class="pt-sec1-box">
<div class="pt-sec1-label">🔘 ボタン・矢印のホバーアニメーション</div>
<div class="pt-sec1-visual">
<div class="pt-sec1-item">
<p class="pt-sec1-title">へこむボタン(沈み込み)</p>
<button class="pt-sec1-btn pt-sec1-btn-dent">CLICK ME</button>
</div>
<div class="pt-sec1-item">
<p class="pt-sec1-title">光るボタン(シャイン)</p>
<button class="pt-sec1-btn pt-sec1-btn-shine">HOVER ME</button>
</div>
<div class="pt-sec1-item">
<p class="pt-sec1-title">矢印が伸びる(移動)</p>
<a href="#" class="pt-sec1-btn pt-sec1-btn-arrow">
READ MORE <span class="pt-sec1-arrow">→</span>
</a>
</div>
</div>
<div class="pt-sec1-code">
/* ⬇️ へこむボタン(transformとbox-shadowの調整) */<br>
<span class="pt-hl-blue">.btn-dent</span> {<br>
<span class="pt-hl-green">box-shadow: 0 6px 0 #0a58ca;</span><br>
<span class="pt-hl-green">transition: all 0.2s ease;</span><br>
}<br>
<span class="pt-hl-blue">.btn-dent:active</span> { /* ホバーではなくクリック時(:active)がおすすめ */<br>
<span class="pt-hl-green">transform: translateY(4px);</span> /* 下へ移動 */<br>
<span class="pt-hl-green">box-shadow: 0 2px 0 #0a58ca;</span> /* 影を短くする */<br>
}<br><br>
/* ➡️ 矢印が伸びる(translateXで移動) */<br>
<span class="pt-hl-blue">.btn-arrow .arrow</span> {<br>
<span class="pt-hl-green">display: inline-block;</span><br>
<span class="pt-hl-green">transition: transform 0.3s ease;</span><br>
}<br>
<span class="pt-hl-blue">.btn-arrow:hover .arrow</span> {<br>
<span class="pt-hl-green">transform: translateX(10px);</span> /* 右へ10px移動 */<br>
}
</div>
</div>
</div>
</div>CSSコード表示
.pt-sec1-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.pt-sec1-demo-area {
display: flex;
justify-content: center;
}
.pt-sec1-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.pt-sec1-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.pt-sec1-visual {
background-color: #f1f3f5;
padding: 30px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 20px;
}
.pt-sec1-item {
text-align: center;
}
.pt-sec1-title {
font-size: 12px;
font-weight: bold;
color: #555;
margin-bottom: 15px;
}
/* 共通ボタン設定 */
.pt-sec1-btn {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #0d6efd;
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 50px;
font-weight: bold;
text-decoration: none;
cursor: pointer;
position: relative;
}
/* 1. へこむボタン */
.pt-sec1-btn-dent {
box-shadow: 0 6px 0 #0a58ca;
transition: all 0.2s ease;
}
.pt-sec1-btn-dent:active {
transform: translateY(4px);
box-shadow: 0 2px 0 #0a58ca;
}
/* 2. 光るボタン */
.pt-sec1-btn-shine {
overflow: hidden;
}
.pt-sec1-btn-shine::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 50%;
height: 100%;
background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0) 100%);
transform: skewX(-20deg);
transition: all 0.5s;
}
.pt-sec1-btn-shine:hover::before {
left: 150%;
}
/* 3. 矢印が伸びる */
.pt-sec1-arrow {
display: inline-block;
margin-left: 8px;
transition: transform 0.3s ease;
}
.pt-sec1-btn-arrow:hover .pt-sec1-arrow {
transform: translateX(8px);
}
.pt-sec1-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.pt-hl-green {
color: #98c379;
font-weight: bold;
}
.pt-hl-blue {
color: #61afef;
font-weight: bold;
}buttonタグの使い方を詳しく知りたい人は「【HTML】buttonタグの使い方:リンク・CSS装飾・無効化」を一読ください。
テキスト(一文字ずつ表示・グラデーション)の演出
メッセージ性を強調したい場面では、「文字のアニメーション」が活躍します。
特に、タイピングしているように一文字ずつ表示させる演出、左から右へ文字に色が流れるようなグラデーションアニメーションは、ヒーローヘッダー(トップ画面)のキャッチコピーに最適です。
<span>のようなインライン要素(display: inline;)は、CSSの仕様上transform(移動や拡大)が効きません。
テキストを動かす場合は、<span>に対してdisplay: inline-block;を指定してください。
グラデーション・フロー
タイピングアニメーション
.gradient-text {
background-image: linear-gradient(to right, #ff416c 0%, #ff4b2b 50%, #ff416c 100%);
background-size: 200% auto;
background-position: 0% center; /* 💡 必須:初期位置 */
color: transparent;
-webkit-text-fill-color: transparent; /* 💡 必須:バグ対策 */
-webkit-background-clip: text;
background-clip: text;
animation: shineText 3s linear infinite;
}
HTMLコード表示
<div class="pt-sec2-wrapper">
<div class="pt-sec2-demo-area">
<div class="pt-sec2-box">
<div class="pt-sec2-label">🔤 テキストアニメーション</div>
<div class="pt-sec2-visual">
<div style="text-align:center; margin-bottom:15px;">
<button id="pt-sec2-trigger" class="pt-sec2-btn">↻ もう一度再生</button>
</div>
<div class="pt-sec2-item">
<p class="pt-sec2-title">グラデーション・フロー</p>
<div class="pt-sec2-gradient-text">BEAUTIFUL DESIGN</div>
</div>
<div class="pt-sec2-item">
<p class="pt-sec2-title">タイピングアニメーション</p>
<div class="pt-sec2-typing-wrap">
<div class="pt-sec2-typing">WELCOME</div>
</div>
</div>
</div>
<div class="pt-sec2-code">
/* 💡 グラデーションアニメの設定 */<br>
<span class="pt-hl-blue">.gradient-text</span> {<br>
<span class="pt-hl-green">background-image: linear-gradient(to right, #ff416c 0%, #ff4b2b 50%, #ff416c 100%);</span><br>
<span class="pt-hl-green">background-size: 200% auto;</span><br>
<span class="pt-hl-green">background-position: 0% center;</span> /* 💡 必須:初期位置 */<br>
<span class="pt-hl-green">color: transparent;</span><br>
<span class="pt-hl-green">-webkit-text-fill-color: transparent;</span> /* 💡 必須:バグ対策 */<br>
<span class="pt-hl-green">-webkit-background-clip: text;</span><br>
<span class="pt-hl-green">background-clip: text;</span><br>
<span class="pt-hl-green">animation: shineText 3s linear infinite;</span><br>
}
</div>
</div>
</div>
</div>CSSコード表示
.pt-sec2-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.pt-sec2-demo-area {
display: flex;
justify-content: center;
}
.pt-sec2-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.pt-sec2-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.pt-sec2-visual {
background-color: #f1f3f5;
padding: 30px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
display: flex;
flex-direction: column;
gap: 30px;
align-items: center;
}
.pt-sec2-btn {
background-color: #0d6efd;
color: #fff;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
}
.pt-sec2-item {
text-align: center;
width: 100%;
}
.pt-sec2-title {
font-size: 12px;
font-weight: bold;
color: #555;
margin-bottom: 15px;
}
/* ====================================
1. グラデーションテキスト
==================================== */
.pt-sec2-gradient-text {
/* 💡 幅を文字にフィットさせる */
display: inline-block;
font-size: 28px;
font-weight: 900;
margin: 0;
background-image: linear-gradient(90deg, #00c6ff 0%, #ff416c 50%, #00c6ff 100%);
/* 背景を2倍の横幅にする */
background-size: 200% 100%;
background-position: 0% 50%;
/* 文字で背景を切り抜く */
color: transparent;
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-clip: text;
/* アニメーションの実行 */
animation: shineText 3s linear infinite;
}
/* 💡 キーフレームもすべて%で統一 */
@keyframes shineText {
0% {
background-position: 0% 50%;
}
100% {
background-position: 200% 50%;
}
}
/* ====================================
2. タイピングアニメーション
==================================== */
.pt-sec2-typing-wrap {
display: inline-block;
}
.pt-sec2-typing {
font-size: 28px;
font-weight: 900;
margin: 0;
color: #333;
overflow: hidden;
white-space: nowrap;
border-right: 3px solid transparent;
width: 0;
}
.pt-sec2-typing.is-active {
animation: typingAnim 2.0s steps(7) forwards, blinkCursor 0.8s step-end infinite;
}
@keyframes typingAnim {
0% {
width: 0;
}
100% {
width: 10ch;
}
}
@keyframes blinkCursor {
0%, 100% {
border-color: transparent;
}
50% {
border-color: #333;
}
}
/* コード表示部分の装飾 */
.pt-sec2-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.pt-hl-green {
color: #98c379;
font-weight: bold;
}
.pt-hl-blue {
color: #61afef;
font-weight: bold;
}
.pt-hl-red {
color: #e06c75;
font-weight: bold;
}background-imageプロパティの使い方を詳しく知りたい人は「【CSS】background-imageの使い方:サイズ・透過・レスポンシブ」を一読ください。
また、CSSグラデーションの作り方を詳しく知りたい人は「【CSS】グラデーションの作り方:文字・背景・枠線・アニメーション」を一読ください。
画像・背景・線が伸びるアニメーション
カードやサムネイル写真にマウスを乗せた時、画像が少し拡大するズームアニメーション、背景回転アニメーションは、クリック可能であることを直感的に伝えます。
また、グローバルメニューなどのリンクアニメーションにおいて、ホバー時に中央から線が伸びる演出や囲み枠アニメーションも実務で多く使われます。
例えば、線が伸びるアニメーションは疑似要素(::after)で引いた線に対してtransform: scaleX(0)からtransform: scaleX(1)へと変形させることで実装します。
transform-origin: center;を指定すれば中央からleftを指定すれば左から滑らかに伸びます。
画像のズームイン(ホバー)
PHOTO
中央から伸びる下線
.link::after {
content: “”;
position: absolute;
bottom: 0; left: 0;
width: 100%; height: 2px;
background-color: #0d6efd;
transform: scaleX(0); /* 💡 初期状態は縮めて見えなくする */
transform-origin: center; /* 💡 中央を起点にする */
transition: transform 0.3s ease;
}
.link:hover::after {
transform: scaleX(1); /* 💡 ホバーで本来の幅に戻す */
}
HTMLコード表示
<div class="pt-sec3-wrapper">
<div class="pt-sec3-demo-area">
<div class="pt-sec3-box">
<div class="pt-sec3-label">🖼 画像・線のホバーアニメーション</div>
<div class="pt-sec3-visual">
<div class="pt-sec3-item">
<p class="pt-sec1-title">画像のズームイン(ホバー)</p>
<div class="pt-sec3-card">
<div class="pt-sec3-card-bg"></div>
<p class="pt-sec3-card-text">PHOTO</p>
</div>
</div>
<div class="pt-sec3-item">
<p class="pt-sec1-title">中央から伸びる下線</p>
<nav>
<a href="#" class="pt-sec3-link">COMPANY</a>
<a href="#" class="pt-sec3-link">SERVICES</a>
</nav>
</div>
</div>
<div class="pt-sec1-code">
/* 📏 中央から伸びる下線(scaleXの活用) */<br>
<span class="pt-hl-blue">.link::after</span> {<br>
<span class="pt-hl-green">content: "";</span><br>
<span class="pt-hl-green">position: absolute;</span><br>
<span class="pt-hl-green">bottom: 0; left: 0;</span><br>
<span class="pt-hl-green">width: 100%; height: 2px;</span><br>
<span class="pt-hl-green">background-color: #0d6efd;</span><br>
<span class="pt-hl-red">transform: scaleX(0);</span> /* 💡 初期状態は縮めて見えなくする */<br>
<span class="pt-hl-red">transform-origin: center;</span> /* 💡 中央を起点にする */<br>
<span class="pt-hl-green">transition: transform 0.3s ease;</span><br>
}<br>
<span class="pt-hl-blue">.link:hover::after</span> {<br>
<span class="pt-hl-green">transform: scaleX(1);</span> /* 💡 ホバーで本来の幅に戻す */<br>
}
</div>
</div>
</div>
</div>CSSコード表示
.pt-sec3-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.pt-sec3-demo-area {
display: flex;
justify-content: center;
}
.pt-sec3-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.pt-sec3-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.pt-sec3-visual {
background-color: #f1f3f5;
padding: 30px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 30px;
}
.pt-sec3-item {
text-align: center;
}
/* 1. 画像のズーム */
.pt-sec3-card {
width: 200px;
height: 120px;
position: relative;
border-radius: 8px;
overflow: hidden; /* はみ出した画像をカット */
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.pt-sec3-card-bg {
position: absolute;
inset: 0;
background-image: url("https://picsum.photos/id/1015/400/300");
background-size: cover;
background-position: center;
transition: transform 0.4s ease;
z-index: 1;
}
/* ホバー時に背景用レイヤーだけを拡大 */
.pt-sec3-card:hover .pt-sec3-card-bg {
transform: scale(1.15);
}
.pt-sec3-card-text {
position: relative;
z-index: 2;
color: #fff;
font-weight: bold;
letter-spacing: 2px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
/* 2. 中央から伸びる下線 */
.pt-sec3-link {
display: inline-block;
margin: 0 10px;
padding-bottom: 5px;
color: #333;
text-decoration: none;
font-weight: bold;
position: relative;
}
.pt-sec3-link::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #0d6efd;
transform: scaleX(0); /* 見えなくする */
transform-origin: center; /* 中央起点 */
transition: transform 0.3s ease;
}
.pt-sec3-link:hover::after {
transform: scaleX(1); /* 伸ばす */
}ハンバーガーメニュー・アコーディオン等のUIパーツ
スマホサイトで必須のハンバーガーメニューアニメーション、Q&Aコーナーで使われるアコーディオン、情報の切り替えを行うタブ切り替えアニメーションなど、機能を持ったUIパーツです。
例えば、アコーディオンアニメーションはGridのgrid-template-rowsを利用します。
0frから1frへとアニメーションさせることで、中身の高さに関わらずCSSのみで滑らかな開閉を実現できます。
ハンバーガー(クリックで×になる)
アコーディオン(details/summary)
Q. ここをクリックして開く
A. CSSのGrid機能を使うことで、JavaScriptを使わずに高さを滑らかに開閉できます。
.hamburger.is-open span:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.is-open span:nth-child(2) {
opacity: 0; /* 真ん中の線は消す */
}
.hamburger.is-open span:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
HTMLコード表示
<div class="pt-sec4-wrapper">
<div class="pt-sec4-demo-area">
<div class="pt-sec4-box">
<div class="pt-sec4-label">🍔 ハンバーガー & アコーディオン</div>
<div class="pt-sec4-visual">
<div class="pt-sec4-item" style="margin-bottom:30px;">
<p class="pt-sec1-title">ハンバーガー(クリックで×になる)</p>
<button id="pt-sec4-hamburger" class="pt-sec4-hamburger">
<span></span>
<span></span>
<span></span>
</button>
</div>
<div class="pt-sec4-item">
<p class="pt-sec1-title">アコーディオン(details/summary)</p>
<details class="pt-sec4-accordion">
<summary>Q. ここをクリックして開く</summary>
<div class="pt-sec4-content-wrap">
<div class="pt-sec4-content-inner">
<p style="margin:0; font-size:13px;">A. CSSのGrid機能を使うことで、JavaScriptを使わずに高さを滑らかに開閉できます。</p>
</div>
</div>
</details>
</div>
</div>
<div class="pt-sec1-code">
/* 🍔 ハンバーガーが×になる */<br>
<span class="pt-hl-blue">.hamburger.is-open span:nth-child(1)</span> {<br>
<span class="pt-hl-green">transform: translateY(8px) rotate(45deg);</span><br>
}<br>
<span class="pt-hl-blue">.hamburger.is-open span:nth-child(2)</span> {<br>
<span class="pt-hl-green">opacity: 0;</span> /* 真ん中の線は消す */<br>
}<br>
<span class="pt-hl-blue">.hamburger.is-open span:nth-child(3)</span> {<br>
<span class="pt-hl-green">transform: translateY(-8px) rotate(-45deg);</span><br>
}
</div>
</div>
</div>
</div>CSSコード表示
.pt-sec4-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.pt-sec4-demo-area {
display: flex;
justify-content: center;
}
.pt-sec4-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.pt-sec4-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.pt-sec4-visual {
background-color: #f1f3f5;
padding: 30px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
display: flex;
flex-direction: column;
align-items: center;
}
/* 1. ハンバーガーメニュー */
.pt-sec4-hamburger {
background: none;
border: none;
width: 40px;
height: 40px;
cursor: pointer;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding: 5px;
}
.pt-sec4-hamburger span {
display: block;
width: 100%;
height: 3px;
background-color: #333;
border-radius: 2px;
transition: all 0.3s ease;
}
/* 開いた状態(JSでクラス付与) */
.pt-sec4-hamburger.is-open span:nth-child(1) {
transform: translateY(9px) rotate(45deg);
}
.pt-sec4-hamburger.is-open span:nth-child(2) {
opacity: 0;
}
.pt-sec4-hamburger.is-open span:nth-child(3) {
transform: translateY(-9px) rotate(-45deg);
}
/* 2. 滑らかなアコーディオン(CSS Grid手法) */
.pt-sec4-accordion {
width: 100%;
min-width: 400px;
background-color: #fff;
border: 1px solid #ced4da;
border-radius: 4px;
}
.pt-sec4-accordion summary {
width: 100%;
min-width: 400px;
padding: 15px;
font-weight: bold;
cursor: pointer;
list-style: none; /* デフォルトの三角形を消す */
position: relative;
}
.pt-sec4-accordion summary::-webkit-details-marker {
display: none;
}
.pt-sec4-accordion summary::after {
content: "+";
position: absolute;
right: 15px;
transition: transform 0.3s;
}
.pt-sec4-accordion[open] summary::after {
transform: rotate(45deg); /* +を×にする */
}
/* Gridを使ったアニメーションの肝 */
.pt-sec4-content-wrap {
display: grid;
grid-template-rows: 0fr; /* 初期は0 */
transition: grid-template-rows 0.3s ease;
}
.pt-sec4-accordion[open] .pt-sec4-content-wrap {
grid-template-rows: 1fr; /* 開くと1frに伸びる */
}
.pt-sec4-content-inner {
overflow: hidden; /* はみ出しを隠す */
padding: 0 15px;
}
.pt-sec4-accordion[open] .pt-sec4-content-inner {
padding-bottom: 15px;
}JavaScriptコード表示
setTimeout(() => {
const hamburger = document.getElementById('pt-sec4-hamburger');
if(hamburger) {
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('is-open');
});
}
}, 100);details・summaryタグの使い方を詳しく知りたい人は「【HTML】details・summaryタグの使い方:アコーディオンとCSSアニメーション」を一読ください。
また、レイアウトに関するGridの使い方を詳しく知りたい人は「【CSS】Gridの使い方:Flexboxとの違いやレスポンシブ」を一読ください。
アニメーションの制御(時間・ループ・遅延)
CSSアニメーションの「設計図(@keyframes)」を作ったら、次は設計図を「どのように再生するか」を細かく指示する必要があります。
再生回数やスピードの調整を間違えると、途端に安っぽくなったり、ユーザーにストレスを与えたりします。
ここでは、アニメーションにおける繰り返しの制御、複数の要素に時間差をつけるテクニックなどを解説します。
- 回数(ループ・1回だけ)と逆再生の設定
- 時間(速度・遅延)とイージングの調整
回数(ループ・1回だけ)と逆再生の設定
アニメーションを何回繰り返すかは、animation-iteration-countプロパティで指定します。
ローディング画面やフワフワ浮くアイコンのように無限ループさせたい場合は、値にinfiniteを指定します。
逆に、画面に登場する際のスライドインなど1 回だけ再生して繰り返さない場合は、デフォルトの1のままで構いません。
1回だけ再生するアニメーションの最後にforwards(animation-fill-mode: forwards;)を付け忘れて、再生後に要素がパッと初期位置に消え去るミスも頻発しますので注意しましょう。
① 1回だけ(forwardsで停止)
② 無限ループ(infinite)
③ 往復・逆再生(alternate + infinite)
@keyframes moveRight {
100% { transform: translateX(200px); }
}
/* ① 1回だけ再生して止まる */
.anim-once {
animation-name: moveRight;
animation-duration: 2s;
animation-iteration-count: 1; /* 1回だけ(初期値) */
animation-fill-mode: forwards; /* 終わった位置で停止 */
}
/* ② 永遠に繰り返す(パッと戻る) */
.anim-infinite {
animation-name: moveRight;
animation-duration: 2s;
animation-iteration-count: infinite; /* 無限ループ */
}
/* ③ 行って戻ってくる(往復) */
.anim-alternate {
animation-name: moveRight;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-direction: alternate; /* 逆再生を許可 */
}
HTMLコード表示
<div class="ctrl-sec1-wrapper">
<div class="ctrl-sec1-demo-area">
<div class="ctrl-sec1-box">
<div class="ctrl-sec1-label">🔁 ループと逆再生の制御</div>
<div class="ctrl-sec1-visual">
<div style="text-align:center; margin-bottom:15px;">
<button id="ctrl-sec1-trigger" class="ctrl-btn">↻ アニメーション開始</button>
</div>
<div class="ctrl-sec1-track">
<p class="ctrl-sec1-title">① 1回だけ(forwardsで停止)</p>
<div class="ctrl-sec1-target ctrl-sec1-once">BOX</div>
</div>
<div class="ctrl-sec1-track">
<p class="ctrl-sec1-title">② 無限ループ(infinite)</p>
<div class="ctrl-sec1-target ctrl-sec1-infinite">BOX</div>
</div>
<div class="ctrl-sec1-track">
<p class="ctrl-sec1-title">③ 往復・逆再生(alternate + infinite)</p>
<div class="ctrl-sec1-target ctrl-sec1-alternate">BOX</div>
</div>
</div>
<div class="ctrl-sec1-code">
/* 💡 共通の設計図(右へ移動) */<br>
<span class="ctrl-hl-blue">@keyframes moveRight</span> {<br>
<span class="ctrl-hl-blue">100%</span> { <span class="ctrl-hl-green">transform: translateX(200px);</span> }<br>
}<br><br>
/* ① 1回だけ再生して止まる */<br>
<span class="ctrl-hl-blue">.anim-once</span> {<br>
<span class="ctrl-hl-green">animation-name: moveRight;</span><br>
<span class="ctrl-hl-green">animation-duration: 2s;</span><br>
<span class="ctrl-hl-red">animation-iteration-count: 1;</span> /* 1回だけ(初期値) */<br>
<span class="ctrl-hl-green">animation-fill-mode: forwards;</span> /* 終わった位置で停止 */<br>
}<br><br>
/* ② 永遠に繰り返す(パッと戻る) */<br>
<span class="ctrl-hl-blue">.anim-infinite</span> {<br>
<span class="ctrl-hl-green">animation-name: moveRight;</span><br>
<span class="ctrl-hl-green">animation-duration: 2s;</span><br>
<span class="ctrl-hl-red">animation-iteration-count: infinite;</span> /* 無限ループ */<br>
}<br><br>
/* ③ 行って戻ってくる(往復) */<br>
<span class="ctrl-hl-blue">.anim-alternate</span> {<br>
<span class="ctrl-hl-green">animation-name: moveRight;</span><br>
<span class="ctrl-hl-green">animation-duration: 2s;</span><br>
<span class="ctrl-hl-green">animation-iteration-count: infinite;</span><br>
<span class="ctrl-hl-red">animation-direction: alternate;</span> /* 逆再生を許可 */<br>
}
</div>
</div>
</div>
</div>CSSコード表示
.ctrl-sec1-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.ctrl-sec1-demo-area {
display: flex;
justify-content: center;
}
.ctrl-sec1-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.ctrl-sec1-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.ctrl-sec1-visual {
background-color: #f1f3f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
.ctrl-btn {
background-color: #0d6efd;
color: #fff;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
}
.ctrl-sec1-track {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px dashed #ced4da;
}
.ctrl-sec1-track:last-child {
margin-bottom: 0;
border-bottom: none;
}
.ctrl-sec1-title {
font-size: 13px;
font-weight: bold;
color: #495057;
margin: 0 0 10px 0;
}
.ctrl-sec1-target {
width: 50px;
height: 40px;
background-color: #198754;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
border-radius: 4px;
}
/* 共通アニメーション */
@keyframes ctrlMoveRight {
0% {
transform: translateX(0);
}
100% {
transform: translateX(250px);
}
}
/* クラス付与用 */
.ctrl-sec1-once.is-active {
animation-name: ctrlMoveRight;
animation-duration: 2s;
animation-timing-function: ease;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
.ctrl-sec1-infinite.is-active {
animation-name: ctrlMoveRight;
animation-duration: 2s;
animation-timing-function: ease;
animation-iteration-count: infinite;
}
.ctrl-sec1-alternate.is-active {
animation-name: ctrlMoveRight;
animation-duration: 2s;
animation-timing-function: ease;
animation-iteration-count: infinite;
animation-direction: alternate;
}
.ctrl-sec1-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.ctrl-hl-green { color: #98c379; font-weight: bold; }
.ctrl-hl-blue { color: #61afef; font-weight: bold; }
.ctrl-hl-red { color: #e06c75; font-weight: bold; }JavaScriptコード表示
setTimeout(() => {
const btn = document.getElementById('ctrl-sec1-trigger');
const targets = document.querySelectorAll('.ctrl-sec1-target');
function replayAnim() {
targets.forEach(target => {
target.classList.remove('is-active');
void target.offsetWidth;
target.classList.add('is-active');
});
}
if(btn) {
btn.addEventListener('click', replayAnim);
}
}, 100);時間(速度・遅延)とイージングの調整
アニメーションの速度はanimation-duration、開始を待たせる遅延はanimation-delayで設定します。
複数の要素に少しずつanimation-delayをズラして指定することで、ドミノ倒しのような時間差を演出できます。
さらに、動きの「緩急」をつけるのがanimation-timing-functionです。
ローディングアイコンや背景の無限スクロールなど、「一定の速度で動き続けてほしい」ものには、css アニメーション linear(等速運動)を指定してください。
easeとlinearの使い分け心地よいUIを作れます。
① イージング(ease vs linear)
❌ ease (カクつく)
⭕️ linear (滑らか)
② ディレイ(animation-delay)による時間差
0.2秒ずつ遅延させることで波のような動きに
.spinner-wrong {
animation-name: spinAnim;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: ease; /* 初期値 */
}
/* ⭕️ 正解:回転アニメは一定速度(linear)にする */
.spinner-correct {
animation-name: spinAnim;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: linear; /* 常に一定 */
}
/* ⏱ 時間差:delayを少しずつズラす */
.dot:nth-child(1) { animation-delay: 0s; }
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }
HTMLコード表示
<div class="ctrl-sec2-wrapper">
<div class="ctrl-sec2-demo-area">
<div class="ctrl-sec2-box">
<div class="ctrl-sec2-label">⏱ イージング(緩急)とディレイ(遅延)</div>
<div class="ctrl-sec2-visual">
<div class="ctrl-sec2-group">
<p class="ctrl-sec2-title">① イージング(ease vs linear)</p>
<div style="display:flex; justify-content:space-around; align-items:center;">
<div style="text-align:center;">
<div class="ctrl-sec2-spinner ctrl-sec2-ease"></div>
<p style="font-size:11px; color:#dc3545; font-weight:bold; margin-top:10px;">❌ ease (カクつく)</p>
</div>
<div style="text-align:center;">
<div class="ctrl-sec2-spinner ctrl-sec2-linear"></div>
<p style="font-size:11px; color:#198754; font-weight:bold; margin-top:10px;">⭕️ linear (滑らか)</p>
</div>
</div>
</div>
<div class="ctrl-sec2-group">
<p class="ctrl-sec2-title">② ディレイ(animation-delay)による時間差</p>
<div class="ctrl-sec2-dots-wrap">
<span class="ctrl-sec2-dot" style="animation-delay: 0s;"></span>
<span class="ctrl-sec2-dot" style="animation-delay: 0.2s;"></span>
<span class="ctrl-sec2-dot" style="animation-delay: 0.4s;"></span>
</div>
<p style="font-size:11px; color:#666; text-align:center; margin-top:10px;">0.2秒ずつ遅延させることで波のような動きに</p>
</div>
</div>
<div class="ctrl-sec1-code">
/* ❌ 失敗:回転アニメにeaseを使うとブレーキがかかる */<br>
<span class="ctrl-hl-blue">.spinner-wrong</span> {<br>
<span class="ctrl-hl-green">animation-name: spinAnim;</span><br>
<span class="ctrl-hl-green">animation-duration: 1s;</span><br>
<span class="ctrl-hl-green">animation-iteration-count: infinite;</span><br>
<span class="ctrl-hl-red">animation-timing-function: ease;</span> /* 初期値 */<br>
}<br><br>
/* ⭕️ 正解:回転アニメは一定速度(linear)にする */<br>
<span class="ctrl-hl-blue">.spinner-correct</span> {<br>
<span class="ctrl-hl-green">animation-name: spinAnim;</span><br>
<span class="ctrl-hl-green">animation-duration: 1s;</span><br>
<span class="ctrl-hl-green">animation-iteration-count: infinite;</span><br>
<span class="ctrl-hl-red">animation-timing-function: linear;</span> /* 常に一定 */<br>
}<br><br>
/* ⏱ 時間差:delayを少しずつズラす */<br>
<span class="ctrl-hl-blue">.dot:nth-child(1)</span> { <span class="ctrl-hl-green">animation-delay: 0s;</span> }<br>
<span class="ctrl-hl-blue">.dot:nth-child(2)</span> { <span class="ctrl-hl-green">animation-delay: 0.2s;</span> }<br>
<span class="ctrl-hl-blue">.dot:nth-child(3)</span> { <span class="ctrl-hl-green">animation-delay: 0.4s;</span> }
</div>
</div>
</div>
</div>CSSコード表示
.ctrl-sec2-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.ctrl-sec2-demo-area {
display: flex;
justify-content: center;
}
.ctrl-sec2-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.ctrl-sec2-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.ctrl-sec2-visual {
background-color: #f1f3f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
.ctrl-sec2-group {
margin-bottom: 25px;
padding-bottom: 20px;
border-bottom: 1px dashed #ced4da;
}
.ctrl-sec2-group:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.ctrl-sec2-title {
font-size: 13px;
font-weight: bold;
color: #495057;
margin: 0 0 15px 0;
text-align: center;
}
/* 1. スピナー(イージング比較) */
.ctrl-sec2-spinner {
width: 40px;
height: 40px;
border: 4px solid #ced4da;
border-top-color: #0d6efd;
border-radius: 50%;
}
@keyframes ctrlSpin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.ctrl-sec2-ease {
animation: ctrlSpin 1s ease infinite;
}
.ctrl-sec2-linear {
animation: ctrlSpin 1s linear infinite;
}
/* 2. ドット(ディレイ) */
.ctrl-sec2-dots-wrap {
display: flex;
justify-content: center;
gap: 10px;
height: 40px;
align-items: center;
}
.ctrl-sec2-dot {
width: 15px;
height: 15px;
background-color: #0d6efd;
border-radius: 50%;
animation: ctrlBounceDot 1.2s infinite ease-in-out;
}
@keyframes ctrlBounceDot {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}スクロールや画面表示に連動させる
CSSアニメーションは強力ですが、そのままでは「ページを読み込んだ瞬間に全てのアニメーションが始まってしまう」という弱点があります。
「ユーザーがその場所までスクロールした時に動かしたい」「ボタンを押したタイミングで動かしたい」といった実装を行うには、javascriptとの連携が不可欠です。
ここでは、実務で必須となるスクロール連動とローディング画面の実装方法を解説します。
- 画面スクロールしたら動かす仕組み
- ローディング画面やページ遷移アニメーション
画面スクロールしたら動かす仕組み
Webサイトをスクロールしていくと、要素がフワッと下から現れる表現は、サイトデザインにおいてよく使われるエフェクトです。
ブラウザではスクロールアニメーションで実装できる仕様(animation-timeline等)も登場しつつありますが、まだ全てのブラウザで動くわけではないため、実務ではJavaScriptを併用して動かすのがスタンダードです。
スクロール連動アニメーションには、最新のJavaScript機能であるIntersection Observer(交差監視API) を使用してください。
これは「要素が画面に入ったかどうか」をブラウザ自身が軽い負荷で見張ってくれる機能です。
画面に入ったら、対象の要素にis-visibleのようなクラスを付与し、CSS側でアニメーションをスタートさせるのが定石です。
↓↓ この枠の中を下にスクロールしてください ↓↓
Scroll Down ⬇️
1. 画面に入りました!
2. 少し遅れて登場!
⬆️ Scroll Up
.js-observe-target {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.8s ease, transform 0.8s ease;
}
/* 💡 JSが付与するクラス:本来の位置と透明度に戻す */
.js-observe-target.is-visible {
opacity: 1;
transform: translateY(0);
}
HTMLコード表示
<div class="link-sec1-wrapper">
<div class="link-sec1-demo-area">
<div class="link-sec1-box">
<div class="link-sec1-label">📜 スクロール連動(画面に入ったら発火)</div>
<div class="link-sec1-visual">
<p style="font-size:12px; color:#dc3545; font-weight:bold; margin-bottom:10px; text-align:center;">
↓↓ この枠の中を下にスクロールしてください ↓↓
</p>
<div class="link-sec1-scroll-container">
<div class="link-sec1-dummy-space">
<p>Scroll Down ⬇️</p>
</div>
<div class="link-sec1-target js-observe-target">
<p>1. 画面に入りました!</p>
</div>
<div class="link-sec1-target js-observe-target" style="transition-delay: 0.2s;">
<p>2. 少し遅れて登場!</p>
</div>
<div class="link-sec1-dummy-space">
<p>⬆️ Scroll Up</p>
</div>
</div>
</div>
<div class="link-sec1-code">
/* 💡 CSS側:初期状態(透明・下にズレている)を作っておく */<br>
<span class="link-hl-blue">.js-observe-target</span> {<br>
<span class="link-hl-green">opacity: 0;</span><br>
<span class="link-hl-green">transform: translateY(30px);</span><br>
<span class="link-hl-green">transition: opacity 0.8s ease, transform 0.8s ease;</span><br>
}<br><br>
/* 💡 JSが付与するクラス:本来の位置と透明度に戻す */<br>
<span class="link-hl-blue">.js-observe-target.is-visible</span> {<br>
<span class="link-hl-green">opacity: 1;</span><br>
<span class="link-hl-green">transform: translateY(0);</span><br>
}
</div>
</div>
</div>
</div>CSSコード表示
.link-sec1-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.link-sec1-demo-area {
display: flex;
justify-content: center;
}
.link-sec1-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.link-sec1-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.link-sec1-visual {
background-color: #f1f3f5;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
/* スクロールデモ用のコンテナ */
.link-sec1-scroll-container {
height: 250px;
overflow-y: auto;
border: 1px solid #ced4da;
background-color: #fff;
border-radius: 4px;
padding: 20px;
/* スムーススクロール */
scroll-behavior: smooth;
}
/* スクロールさせるための余白 */
.link-sec1-dummy-space {
height: 300px;
display: flex;
align-items: center;
justify-content: center;
color: #adb5bd;
font-weight: bold;
font-size: 16px;
}
/* アニメーションのターゲット要素 */
.link-sec1-target {
background-color: #0d6efd;
color: #fff;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
text-align: center;
font-weight: bold;
/* 初期状態(隠しておく) */
opacity: 0;
transform: translateY(40px);
/* transitionで滑らかに動かす準備 */
transition: opacity 0.8s ease, transform 0.8s ease;
}
.link-sec1-target p {
margin: 0;
}
/* JSによって付与される表示用クラス */
.link-sec1-target.is-visible {
opacity: 1;
transform: translateY(0);
}
.link-sec1-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.link-hl-green {
color: #98c379;
font-weight: bold;
}
.link-hl-blue {
color: #61afef;
font-weight: bold;
}JavaScriptコード表示
setTimeout(() => {
// Intersection Observer の設定
const options = {
root: document.querySelector('.link-sec1-scroll-container'), // デモ用に監視基準をコンテナに設定(通常はnullで画面全体)
rootMargin: '0px',
threshold: 0.3 // 要素が30%見えたら発火
};
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
// 画面内に入ったら
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
// 1回動かしたら監視を解除する(何度も上下させる場合は消す)
// obs.unobserve(entry.target);
} else {
// スクロールで戻った時に再度アニメーションさせるためのリセット
entry.target.classList.remove('is-visible');
}
});
}, options);
// ターゲット要素を監視対象に追加
const targets = document.querySelectorAll('.js-observe-target');
targets.forEach(target => {
observer.observe(target);
});
}, 100);ローディング画面やページ遷移アニメーション
Webサイトにアクセスした直後にロゴがフワッと浮かび上がるローディング、別のページへ移動する際に画面が暗転する画面遷移アニメーションもJSとCSSの連携で実装します。
ローディング画面を表示している間は、<body>タグにoverflow: hidden;を付与してスクロールを禁止してください。
ページの読み込み完了(window.onloadなど)を検知したら、スクロール禁止を解除しつつ、ローディング画面に.is-loadedのようなクラスを付けて、transitionまたはanimationを使って滑らかにフェードアウトさせます。
display: none;にして消すのはダサいのでNGです。
LOADING…
ページが読み込まれました!
※ローディング中はスクロールできません。
.loading-overlay {
position: fixed; /* 画面全体を覆う */
inset: 0;
z-index: 9999; /* 一番手前に出す */
background-color: #ffffff;
transition: opacity 0.6s ease, visibility 0.6s ease;
}
/* 💡 読み込み完了時(JSで付与するクラス) */
.loading-overlay.is-loaded {
opacity: 0; /* フワッと消える */
visibility: hidden; /* クリック判定も消す */
}
HTMLコード表示
<div class="link-sec2-wrapper">
<div class="link-sec2-demo-area">
<div class="link-sec2-box">
<div class="link-sec2-label">⏳ ローディングアニメーション</div>
<div class="link-sec2-visual">
<div style="text-align:center; margin-bottom:15px;">
<button id="link-sec2-trigger" class="link-sec2-btn">↻ リロードをシミュレート</button>
</div>
<div class="link-sec2-browser">
<div id="js-loading-overlay" class="link-sec2-loading">
<div class="link-sec2-spinner"></div>
<p class="link-sec2-loading-text">LOADING...</p>
</div>
<div class="link-sec2-content">
<div style="margin-top:0;">Main Content</div>
<p>ページが読み込まれました!</p>
<p>※ローディング中はスクロールできません。</p>
<div style="height:150px; background:#e9ecef; margin-top:20px;"></div>
</div>
</div>
</div>
<div class="link-sec1-code">
/* 💡 ローディング画面(全画面を覆う設定) */<br>
<span class="link-hl-blue">.loading-overlay</span> {<br>
<span class="link-hl-green">position: fixed;</span> /* 画面全体を覆う */<br>
<span class="link-hl-green">inset: 0;</span><br>
<span class="link-hl-green">z-index: 9999;</span> /* 一番手前に出す */<br>
<span class="link-hl-green">background-color: #ffffff;</span><br>
<span class="link-hl-green">transition: opacity 0.6s ease, visibility 0.6s ease;</span><br>
}<br><br>
/* 💡 読み込み完了時(JSで付与するクラス) */<br>
<span class="link-hl-blue">.loading-overlay.is-loaded</span> {<br>
<span class="link-hl-green">opacity: 0;</span> /* フワッと消える */<br>
<span class="link-hl-green">visibility: hidden;</span> /* クリック判定も消す */<br>
}
</div>
</div>
</div>
</div>CSSコード表示
.link-sec2-wrapper {
background-color: #f8f9fa;
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 4px;
font-family: sans-serif;
}
.link-sec2-demo-area {
display: flex;
justify-content: center;
}
.link-sec2-box {
background-color: #ffffff;
border: 2px dashed #adb5bd;
padding: 25px;
width: 100%;
max-width: 600px;
border-radius: 4px;
}
.link-sec2-label {
font-size: 15px;
font-weight: bold;
margin-bottom: 20px;
color: #333;
}
.link-sec2-visual {
background-color: #f1f3f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #dee2e6;
}
.link-sec2-btn {
background-color: #333;
color: #fff;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
}
/* 仮想のブラウザ画面 */
.link-sec2-browser {
position: relative;
height: 250px;
border: 2px solid #ced4da;
border-radius: 8px;
background-color: #fff;
/* overflowをJSで制御してスクロールを止める */
overflow-y: hidden;
}
/* 中身のコンテンツ */
.link-sec2-content {
padding: 20px;
}
.link-sec1-code {
background-color: #282c34;
color: #abb2bf;
padding: 15px;
font-family: monospace;
font-size: 13px;
border-radius: 4px;
line-height: 1.6;
border-left: 4px solid #0d6efd;
}
.link-hl-green {
color: #98c379;
font-weight: bold;
}
.link-hl-blue {
color: #61afef;
font-weight: bold;
}
/* ====================================
ローディング画面本体
==================================== */
.link-sec2-loading {
position: absolute; /* デモ用(実際はfixed) */
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
background-color: #333;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* 消える時の滑らかなアニメーション */
transition: opacity 0.8s ease, visibility 0.8s ease;
}
/* 読み込み完了後にJSが付与するクラス */
.link-sec2-loading.is-loaded {
opacity: 0;
visibility: hidden;
}
/* くるくる回るスピナー */
.link-sec2-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.2);
border-top-color: #fff;
border-radius: 50%;
animation: loadSpin 1s linear infinite;
}
@keyframes loadSpin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.link-sec2-loading-text {
color: #fff;
font-weight: bold;
letter-spacing: 2px;
margin-top: 15px;
font-size: 12px;
}
/* スクロール制御用クラス */
.link-sec2-browser.is-scrollable {
overflow-y: auto; /* ロード完了でスクロール解禁 */
}JavaScriptコード表示
setTimeout(() => {
const btn = document.getElementById('link-sec2-trigger');
const overlay = document.getElementById('js-loading-overlay');
const browser = document.querySelector('.link-sec2-browser');
function simulateLoad() {
// 1. 初期化:ローディング画面を出し、スクロールを止める
overlay.classList.remove('is-loaded');
browser.classList.remove('is-scrollable');
browser.scrollTop = 0; // スクロール位置を上に戻す
// 2. 2秒後に「読み込み完了」と見なす
setTimeout(() => {
// オーバーレイをフェードアウト
overlay.classList.add('is-loaded');
// スクロールを解禁
browser.classList.add('is-scrollable');
}, 2000);
}
if(btn && overlay && browser) {
btn.addEventListener('click', simulateLoad);
// 初期実行
simulateLoad();
}
}, 100);まとめ
分かりやすいようにまとめを記載します。
- アニメーションは
transition(単一の変化)と@keyframes(複数状態・ループ)の2つで実装する。 transitionプロパティは、ホバー時(:hover)ではなくベースとなる要素側に記述する。@keyframesアニメーションを最終到達点で停止させるにはanimation-fill-mode: forwards;を指定する。- 振り子のような動きを作る際は
transform-originで変形の起点(軸)を適切に設定する。 - 画面外からのスライドインを実装する場合は、大枠の要素に
overflow-x: hidden;を指定し横揺れを防ぐ。 - ブラウザの描画負荷を抑えるため、移動や拡大、波紋エフェクトには
widthやbox-shadowではなく、transformとopacityを使用する。 - テキスト(
spanタグ等)にtransformを適用して動かす際は、display: inline-block;の指定が必須となる。 - 往復アニメーションは
alternate、一定速度で動かし続けるローディングや背景にはlinearを指定する。 - スクロール連動アニメーションの実装は、負荷の高い
scrollイベントを避け、JavaScriptの「Intersection Observer」を使用する。 - ローディング画面の待機中は裏側のスクロールを禁止し、読み込み完了後にフェードアウトさせて解除する。
よくある質問(FAQ)
CSSの「transition」と「@keyframes」の違いは何ですか?
大きな違いは「複雑さ」と「ループができるか」です。
transitionは「ホバーした時に色が変わる」といった、AからBへの1回限りのシンプルな状態変化を作るのに適しています。
一方animation(@keyframes)は、「A→B→C→Dと複数の状態を連続して変化させる」場合、「自動でずっと動き続ける無限ループ」など、複雑な演出を作りたい場合に使用します。
CSSアニメーションを無限にループ(繰り返し)させるにはどうすればいいですか?
アニメーションの再生回数を決めるanimation-iteration-countプロパティに、infiniteという値を指定します。
例えば、animation: myAnimation 2s infinite;のように記述することで、2秒間のアニメーションが途切れることなく繰り返されます。
スクロールして画面に見えた時にだけ、フワッとアニメーションさせることはできますか?
はい、可能ですが、実務ではJavaScript(Intersection Observer API)と連携させるのが一般的です。
CSSでは「今どこまでスクロールされたか」を正確に検知することが難しいため、JavaScriptで「対象の要素が画面に入ったらis-visibleというクラスを付与する」という仕組みを作ります。
CSS側では「is-visibleがついたらアニメーションを発火する」と記述するのが確実な手法です。
アニメーションを実装したら、スマホで見るとカクカクして重いです。
アニメーションさせるプロパティを見直すことで軽くなります。
width、height、margin、top/left、box-shadow などを動かすと、ブラウザの描画エンジンに高い負荷がかかりカクつきの原因になります。
スマホでも滑らかに動かすには、GPUで処理されるtransform(移動・拡大・回転)とopacity(透明度)の2つだけを使ってアニメーションさせるのがよいです。
display: noneからdisplay: blockに切り替わる時、フワッとフェードインさせられますか?
いいえ、CSSの仕様上、display プロパティに対して直接transitionなどのアニメーションをかけることはできません。
一瞬でパッと表示されてしまいます。
フワッとフェードインさせたい場合はdisplayを使うのをやめ、代わりにvisibility: hidden;とopacity: 0;から、visibility: visible;とopacity: 1; へと変化させる手法を使うのがよいです。

