ウェブデザインでは、画像やテキストのアニメーションを活用して、ユーザーの目を引くことが重要です。
前回の記事では、CSSを使用したアニメーションの実装方法を紹介しました。
特定のタイミングでアニメーションを発動させるにはJavaScriptが必要です。(ChromeとEdge等のChromiumベースのブラウザにはスクロール駆動型アニメーションの実装があるため、JavaScriptなしでも実現できますが、それについてはまた別の記事で詳しく解説しようと思います。)
今回は、CSSでスクロール時やクリック時にアニメーションを実行する方法を紹介します。
目次
要素が表示されたらアニメーションを開始(フェードイン)
CSSだけではページ読み込み時にアニメーションが開始されるため、スクロールしたときに要素が画面に現れたタイミングでアニメーションを適用するにはJavaScriptを使用します。
まずは、以下のDEMOをご覧ください。
スクロールによって要素が表示されると、それぞれのアニメーションが発動します。
Intersection Observerとは?
Intersection Observer は、要素がビューポート(画面内)に入ったかどうかを検知するためのJavaScript APIです。
scrollとは違いスクロールイベントを頻繁に監視しませんので、パフォーマンスがよくアニメーションのトリガーとして利用できます。
こちらがIntersection Observerを利用したコードとなっています。
<script>
// Intersection Observer を作成
const observer = new IntersectionObserver(
// スクロール時に要素が画面内に入ったらアニメーションを適用
entries => {
entries.forEach(entry => {
// 要素が画面内(ビューポート内)に入ったかどうかを判定
if (entry.isIntersecting) {
// .show クラスを追加してアニメーションを開始
entry.target.classList.add('show');
}
});
}, { threshold: 0.5 }); // 50%以上表示されたら発火
// .animate クラスを持つすべての要素を監視対象に追加
document.querySelectorAll('.animate').forEach(el => observer.observe(el));
</script>
entries
は Intersection Observer API から渡される 配列 で、監視対象のすべての要素に関する情報が格納されています。
entry
は、その 配列の各要素(個々の要素の情報) です。
ここでは、document.querySelectorAll('.animate')
で取得した各HTML要素の情報(タグや位置、可視状態など)が入っています。
// entries
[
// entry
{
target: <div class="animate">要素1</div>, // 実際のHTML要素
isIntersecting: true, // 画面内に入っているかどうか
intersectionRatio: 0.8, // どれくらい表示されているか(0.0〜1.0)
boundingClientRect: { ... }, // 要素の位置とサイズ
rootBounds: { ... }, // ビューポート(画面)の位置とサイズ
time: 3456.78 // イベント発生時間
},
// entry
{
target: <img class="animate" src="image.jpg" alt="要素3">,
isIntersecting: false, // まだ入っていない
intersectionRatio: 0.0,
boundingClientRect: { ... },
rootBounds: { ... },
time: 3456.78
}
]
HTMLとCSS(参考のため余計な箇所は除いています)
<div class="container">
<div class="box animate fade-in">
<div class="label">フェードイン</div>
<div class="image-box"><img src="sample.png"></div>
</div>
<div class="box animate zoom-in">
<div class="label">ズームイン</div>
<div class="image-box"><img src="sample.png"></div>
</div>
<div class="box animate slide-in">
<div class="label">スライドイン</div>
<div class="image-box"><img src="sample.png"></div>
</div>
<div class="box animate rotate">
<div class="label">回転</div>
<div class="image-box"><img src="sample.png"></div>
</div>
</div>
/* --------------------------
【初期状態】 アニメーションを適用する要素
-------------------------- */
.animate {
opacity: 0; /* 初期状態では非表示 */
transition: opacity 3s ease-out, transform 3s ease-out; /* 透明度と変形を3秒かけてスムーズに変化 */
}
/* フェードインアニメーション(下から上に移動しながら表示) */
.fade-in {
transform: translateY(120px); /* 初期状態では下方向に120pxずらす */
}
/* ズームインアニメーション(拡大しながら表示) */
.zoom-in {
transform: scale(0.1); /* 初期状態では10%のサイズ */
}
/* スライドインアニメーション(右から左へ移動しながら表示) */
.slide-in {
transform: translateX(150px); /* 初期状態では右に150pxずらす */
}
/* 3D回転アニメーション(Y軸を中心に回転しながら表示) */
.rotate {
transform: rotateY(90deg); /* 初期状態ではY軸方向に90度回転 */
}
/* --------------------------
【最終状態】要素が画面内に入ったとき(アニメーション完了時)
-------------------------- */
.show {
opacity: 1; /* 完全に表示 */
transform: translateY(0) scale(1) translateX(0) rotateY(0); /* 元の位置・サイズ・回転に戻す */
}
【初期状態】各要素を非表示にしてずらす
- .animate クラスが適用された要素は opacity: 0 なので 最初は非表示
- さらに transform を適用し、各アニメーションごとに 位置やサイズ、回転を変更
- .fade-in → 120px下にずらしておく
- .zoom-in → 10%のサイズに縮小しておく
- .slide-in → 右側に150pxずらしておく
- .rotate → Y軸に90度回転しておく
【最終状態】(フェードイン)スクロールして要素が画面内に表示で戻す
- Intersection Observer によって、要素が 画面の50%以上表示されたら show クラスを追加
- .show クラスが追加されると、opacity: 1 になり transform がリセットされて 元の位置・サイズ・回転に戻る
- transition: opacity 3s ease-out, transform 3s ease-out; によって、 3秒かけて戻していく
といった流れになります。
動きを調整したい時は、ずらす位置(transform)や、戻す時間(transition)で調整します。
続いてscrollイベントの例も紹介します。
スクロールで色が変わっていく
スクロールに応じて要素の背景色が徐々に変化していくアニメーション。
次はscrollイベントを利用して作成します。まずはDemoを御覧ください。
Scrollイベントとは?
Intersection Observer は、要素がビューポート(画面内)に入った段階で検知(発火)するのに対して、スクロール (scroll
) イベントとは、ユーザーがページをスクロールしたときに発火するイベントです。※スクロールする度にイベントが発火します。
これを使うことで、スクロール量に応じて 背景色を変えたり、要素をアニメーションさせたり できます。
シンプルな scroll
イベントの例
scroll
イベントを window
に追加することで、スクロール時に処理を実行できます。
ユーザーがスクロールするたびに「スクロールされました!」がコンソールに表示
// scrollイベントをwindowに追加
window.addEventListener('scroll', () => {
console.log("スクロールされました!");
});
スクロール量を取得して100px になったら処理を実行
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
console.log("100px以上スクロールしました!");
}
});
DEMOの解説
このスクリプトでは、スクロールに応じてページの背景色が変化 するようになっています。
ページのスクロール量を取得し、それに応じて RGBの色を補間しながら変更 します。
body, html {
margin: 0;
padding: 0;
height: 300vh;
background-color: rgb(240, 160, 200); /* 初期色を適用 */
}
<script>
// スクロールイベントを追加(ページをスクロールすると発火)
window.addEventListener('scroll', () => {
// スクロール可能な最大の長さ(ページ全体の高さ - 画面の高さ)
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
// 現在のスクロール量を ユーザーが スクロール可能な高さ(px)割合 0.0 ~ 1.0 の範囲で取得
const scrollPercent = window.scrollY / maxScroll;
// スクロール量に応じてRGBの値を変更(赤 → 緑 → 青)
const red = Math.floor(240 - (scrollPercent * 150)); // 240 → 90(赤を減少)
const green = Math.floor(160 + (scrollPercent * 60)); // 160 → 220(緑を増加)
const blue = Math.floor(200 + (scrollPercent * 40)); // 200 → 240(青を増加)
// 計算した RGB の値を背景色に適用
document.body.style.backgroundColor = `rgb(${red}, ${green}, ${blue})`;
});
</script>
スクロール可能な最大範囲を計算
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
document.documentElement.scrollHeight
→ ページ全体の高さwindow.innerHeight
→ 現在の画面の高さ- 最大スクロール可能な範囲 を求めることで、どのくらいスクロールできるか を取得。
(例)ページの高さ: 3000px 画面の高さ: 800px
maxScroll = 3000 – 800 = 2200px(スクロールできる最大値)
スクロールの進行度(0.0 ~ 1.0)を取得
const scrollPercent = window.scrollY / maxScroll;
window.scrollY
→ 現在のスクロール位置(px単位)maxScroll
で割ることで、スクロールの進行度を 0.0(最上部)~ 1.0(最下部) の範囲で取得。
(例)スクロール位置と進行度
スクロール位置 (window.scrollY ) | 進行度(scrollPercent ) |
---|---|
0px (最上部) | 0.0 |
1100px (中央付近) | 0.5 |
2200px (最下部) | 1.0 |
RGB値の計算
const red = Math.floor(240 - (scrollPercent * 150)); // 赤成分を減少(240 → 90)
const green = Math.floor(160 + (scrollPercent * 60)); // 緑成分を増加(160 → 220)
const blue = Math.floor(200 + (scrollPercent * 40)); // 青成分を増加(200 → 240)
// 計算した RGB の値を背景色に適用
document.body.style.backgroundColor = `rgb(${red}, ${green}, ${blue})`;
これでスクロール量 (scrollPercent
) に応じて、RGBの各値を変化させbackgroud
に適用させています。
スクロールで画像を大きくして、最後にClickイベントでアニメーション
最後に応用編として、スクロールに応じて要素を大きくしていき、ある程度大きくなったところで薄くする、最後にクリックされたら回転して消える。
イベントとアニメーションをいくつか実装したものです。
まずはDemoを御覧ください。
DEMO解説
スクロールに応じて画像が 拡大(scale(0)
→ scale(1.0)
) し、
最大サイズになったら 徐々に透明度が低下(opacity: 1
→ opacity: 0.3
) する。
最後に「いかがでしたか?」の文字が フェードイン するアニメーションを実装し、クリックイベントを有効化します。
クリックされたら回転して右斜上に消えていきます。(disappear
)
<html>
<body>
<div class="image-container"><img src="sample.png"></div>
<div class="text-overlay">いかがでしたか?</div>
</body>
</html>
/* 画像コンテナの初期状態(スクロール前) */
.image-container {
position: fixed; /* 画面に固定 */
top: 50%; /* 要素の角を画面の中央に */
left: 50%;
/* 要素分半分ずらして真ん中に、初期状態は小さく縮小→大きく拡大していく */
transform: translate(-50%, -50%) scale(0);
opacity: 1; /* 初期状態は完全に表示→最終状態で薄くする */
transition: transform 0.1s linear, opacity 0.5s ease-out; /* 拡大と透明度変化を滑らかに */
}
/* 画像サイズ設定 */
.image-container img {
width: 300px; /* 最大サイズ */
height: auto;
pointer-events: none; /* 初期状態ではクリック不可 */
}
/* テキストの初期状態(非表示) */
.text-overlay {
position: fixed; /* 画面に固定 */
top: 50%; /* 画面の中央 */
left: 50%;
transform: translate(-50%, -50%);
opacity: 0; /* 初期状態では見えない →最終状態で見えるように*/
transition: opacity 0.5s ease-out; /* フェードインを滑らかに */
font-size: 24px;
font-weight: bold;
color: black;
}
/* クリックイベントのアニメーション(回転しながら右斜め上へ飛び、小さくなり消える) */
.disappear {
/* translateで200px右上に移動、scale(0)で縮小して消える、rotate(720deg)で2回転する */
transform: translate(200px, -200px) scale(0) rotate(720deg);
/* 完全に非表示になる */
opacity: 0;
/* transformとopacity で定義された動作を1秒かけて、スムーズに変化させる */
transition: transform 1s ease-out, opacity 1s ease-out;
}
<script>
// スクロールイベントを追加(ページをスクロールすると発火)
window.addEventListener('scroll', () => {
// ------------------------------------
// スクロール位置と計算
// ------------------------------------
// スクロール可能な最大の長さ(ページ全体の高さ - 画面の高さ)
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
// 現在のスクロール量を 0.0 ~ 1.0 の範囲で取得
const scrollPercent = window.scrollY / maxScroll;
// ------------------------------------
// 画像の拡大と透明度
// ------------------------------------
// 画像の拡大(0 → 1.0)
// スクロールが進むほど scale が増加し、最大で 1.0(等倍)まで拡大
const scale = Math.min(scrollPercent * 2, 1);
// 透明度を減少させる(最大サイズの50%を超えたら透明化開始)
const fadeStart = 0.5; // 50% スクロール時にフェード開始
const opacity = scrollPercent < fadeStart ? 1 // 50% より前なら透明度は 1(完全に表示)
: Math.max(1 - (scrollPercent - fadeStart) * 1.8, 0.1); // 最低 0.1 まで薄くする
// ------------------------------------
// 画像の拡大と透明度変更を適用
// ------------------------------------
document.querySelector('.image-container').style.transform = `translate(-50%, -50%) scale(${scale})`;
document.querySelector('.image-container').style.opacity = opacity;
// ------------------------------------
// テキストのフェードイン(スクロールの60%以降)
// ------------------------------------
// 60% を超えると opacity が増加し、徐々に表示
const textOpacity = Math.max(scrollPercent - 0.6, 0) * 2;
document.querySelector('.text-overlay').style.opacity = textOpacity;
// ------------------------------------
// スクロールが100%(最下部)に達したらクリックを有効化
// ------------------------------------
if (scrollPercent >= 1) {
document.querySelector('.image-container img').style.pointerEvents = "auto";
}
});
// ------------------------------------
// 画像クリックイベントの追加
// ------------------------------------
document.querySelector('.image-container img').addEventListener('click', () => {
document.querySelector('.image-container img').classList.add('disappear');
});
</script>
まとめ
今回の記事では、CSSでアニメーションを定義し、JavaScriptを使って起動タイミングに応じたアニメーションを作成する方法 を解説しました。
方法 | 概要 |
---|---|
CSS @keyframes | ページロード時からループアニメーション |
CSS transform | 要素の位置・回転・拡大縮小を変化させる |
CSS opacity | 要素の透明度 |
CSS transition | 指定した要素の変化をスムーズにする |
JavaScript Intersection Observer | 要素が画面内に入ったらアニメーション |
JavaScript scroll イベント | スクロール量に応じてアニメーション |
JavaScript click イベント | クリック時にアニメーション |
特に以下のポイントを押さえることで、動的なアニメーションを効率よく作ることができます
アニメーションの基本は「初期状態 → 最終状態」を考える
- 初期状態(画面ロード時の要素の位置、サイズ、透明度を設定)
- 最終状態を定義(CSSの
transition
やtransform
を適用)
アニメーションの発火方法を選ぶ
- Intersection Observer → スクロールして要素が表示されたときに実行
- scroll イベント → スクロール量に応じた動きを作る
- click イベント → ユーザーのアクションに応じた動きを作る
- 負荷を考える場合は、Intersection Observerをできるだけ使いましょう
どうでしょうか?
いろいろなアニメーションが、意外と簡単に作成できます。
普段はライブラリを利用している方も多いと思いますが、基本を理解すれば、これらを応用してより魅力的なアニメーションを自在に作ることができます。
ぜひ試してみてください!