第7回:CSS装飾とアニメーション
🎯 学習目標
- transformプロパティを理解し活用できる
- transitionとanimationの違いを理解する
- 疑似要素・疑似クラスを使いこなせる
- パフォーマンスを考慮したアニメーションが作れる
📚 導入(5分)
ユーザーエクスペリエンスとアニメーション
アニメーションは単なる装飾ではなく、重要なUX要素です:
- フィードバック:ユーザーの操作に対する応答
- 注意誘導:重要な要素への注目を促す
- 状態変化:画面の変化を自然に見せる
- ブランディング:サイトの個性を表現
パフォーマンスを考慮したアニメーション
/* ✅ GPUで処理される(高速) */
transform: translateX(100px);
transform: scale(1.2);
opacity: 0.5;
/* ❌ CPUで処理される(低速) */
left: 100px;
width: 200px;
height: 100px;💡 理論学習(30分)
transformプロパティ(15分)
translate:移動
.element {
/* X軸方向に移動 */
transform: translateX(50px);
/* Y軸方向に移動 */
transform: translateY(-30px);
/* X・Y軸同時に移動 */
transform: translate(50px, -30px);
/* Z軸方向に移動(3D) */
transform: translateZ(10px);
/* 3軸同時に移動 */
transform: translate3d(50px, -30px, 10px);
/* パーセント指定(要素自身のサイズ基準) */
transform: translate(50%, -50%); /* 中央配置によく使用 */
}rotate:回転
.element {
/* Z軸周りに回転(2D回転) */
transform: rotate(45deg);
transform: rotate(0.25turn); /* 1turn = 360deg */
transform: rotate(50grad); /* 1grad = 0.9deg */
/* X軸周りに回転 */
transform: rotateX(45deg);
/* Y軸周りに回転 */
transform: rotateY(45deg);
/* Z軸周りに回転 */
transform: rotateZ(45deg);
/* 3D回転 */
transform: rotate3d(1, 1, 0, 45deg); /* x, y, z, 角度 */
}scale:拡大縮小
.element {
/* 均等拡大縮小 */
transform: scale(1.5); /* 1.5倍 */
transform: scale(0.8); /* 0.8倍 */
/* X・Y軸個別指定 */
transform: scaleX(2); /* X軸のみ2倍 */
transform: scaleY(0.5); /* Y軸のみ0.5倍 */
transform: scale(2, 0.5); /* X軸2倍、Y軸0.5倍 */
/* Z軸拡大縮小 */
transform: scaleZ(2);
/* 3D拡大縮小 */
transform: scale3d(2, 0.5, 1);
}skew:傾斜
.element {
/* X軸方向に傾斜 */
transform: skewX(30deg);
/* Y軸方向に傾斜 */
transform: skewY(15deg);
/* X・Y軸同時に傾斜 */
transform: skew(30deg, 15deg);
}複数変形の組み合わせ
.element {
/* スペース区切りで複数指定 */
transform: translate(50px, 100px) rotate(45deg) scale(1.2);
/* 適用順序に注意:右から左に適用される */
transform: scale(1.2) rotate(45deg) translate(50px, 100px);
}transform-origin:変形の基準点
.element {
/* デフォルトは中央(50% 50%) */
transform-origin: center center;
/* 左上を基準点にする */
transform-origin: top left;
transform-origin: 0 0;
/* 右下を基準点にする */
transform-origin: bottom right;
transform-origin: 100% 100%;
/* カスタム基準点 */
transform-origin: 20px 30px;
/* 回転の基準点を変更 */
transform: rotate(45deg);
transform-origin: top left; /* 左上を軸に回転 */
}transitionとanimation(15分)
transition:状態変化のアニメーション
.button {
background-color: #3498db;
transform: scale(1);
transition: all 0.3s ease;
}
.button:hover {
background-color: #2980b9;
transform: scale(1.05);
}
/* 個別プロパティで詳細制御 */
.button-detailed {
background-color: #3498db;
transform: scale(1);
/* プロパティ別に設定 */
transition-property: background-color, transform;
transition-duration: 0.3s, 0.2s;
transition-timing-function: ease, ease-out;
transition-delay: 0s, 0.1s;
}timing-function:イージング
.element {
transition: transform 0.5s ease; /* ゆっくり始まり、ゆっくり終わる */
transition: transform 0.5s ease-in; /* ゆっくり始まる */
transition: transform 0.5s ease-out; /* ゆっくり終わる */
transition: transform 0.5s ease-in-out; /* ゆっくり始まり、ゆっくり終わる */
transition: transform 0.5s linear; /* 一定速度 */
/* カスタムベジエ曲線 */
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* ステップ関数 */
transition: transform 1s steps(10, end); /* 10段階でアニメーション */
}animation:キーフレームアニメーション
/* キーフレームの定義 */
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(30px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* from/to記法 */
@keyframes slideIn {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* 複数ステップ */
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
}
/* アニメーションの適用 */
.element {
animation: fadeInUp 0.6s ease-out;
}
/* 詳細制御 */
.element-detailed {
animation-name: bounce;
animation-duration: 2s;
animation-timing-function: ease;
animation-delay: 0.5s;
animation-iteration-count: infinite; /* 無限ループ */
animation-direction: alternate; /* 往復 */
animation-fill-mode: forwards; /* 最終状態を維持 */
animation-play-state: paused; /* 一時停止 */
}疑似要素・疑似クラス
/* 疑似クラス */
.button:hover { /* ホバー時 */ }
.input:focus { /* フォーカス時 */ }
.checkbox:checked { /* チェック時 */ }
.element:first-child { /* 最初の子要素 */ }
.element:nth-child(2n) { /* 偶数番目 */ }
/* 疑似要素 */
.element::before {
content: "";
display: block;
/* 要素の前に挿入 */
}
.element::after {
content: "";
display: block;
/* 要素の後に挿入 */
}
.paragraph::first-letter {
font-size: 2em;
float: left;
/* 最初の文字を装飾 */
}🛠️ 実習(50分)
インタラクティブカードコンポーネント作成(45分)
HTML構造
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>インタラクティブカード</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<h1 class="page-title">Interactive Cards</h1>
<div class="cards-grid">
<!-- フリップカード -->
<div class="card card--flip">
<div class="card__inner">
<div class="card__front">
<div class="card__content">
<h2 class="card__title">フリップカード</h2>
<p class="card__text">ホバーで裏面が表示されます</p>
<div class="card__icon">🎭</div>
</div>
</div>
<div class="card__back">
<div class="card__content">
<h2 class="card__title">裏面</h2>
<p class="card__text">これは裏面の内容です。詳細情報をここに表示できます。</p>
<button class="card__button">詳細を見る</button>
</div>
</div>
</div>
</div>
<!-- グロウカード -->
<div class="card card--glow">
<div class="card__content">
<h2 class="card__title">グロウエフェクト</h2>
<p class="card__text">ホバーで光るエフェクト</p>
<div class="card__icon">✨</div>
</div>
</div>
<!-- スライドカード -->
<div class="card card--slide">
<div class="card__image">
<img src="https://picsum.photos/300/200?random=1" alt="カード画像">
</div>
<div class="card__content">
<h2 class="card__title">スライドカード</h2>
<p class="card__text">コンテンツがスライドして表示</p>
</div>
<div class="card__overlay">
<div class="card__overlay-content">
<h3>詳細情報</h3>
<p>ホバー時に表示される詳細な情報です。</p>
<button class="card__button">もっと見る</button>
</div>
</div>
</div>
<!-- バウンスカード */
<div class="card card--bounce">
<div class="card__content">
<h2 class="card__title">バウンスアニメーション</h2>
<p class="card__text">クリックでバウンスします</p>
<div class="card__icon">🏀</div>
</div>
</div>
<!-- ロードカード -->
<div class="card card--loading">
<div class="card__content">
<h2 class="card__title">ローディング</h2>
<p class="card__text">ローディングアニメーション</p>
<div class="loading-spinner"></div>
</div>
</div>
<!-- 3Dカード -->
<div class="card card--3d">
<div class="card__content">
<h2 class="card__title">3Dエフェクト</h2>
<p class="card__text">立体的な変形エフェクト</p>
<div class="card__icon">🎲</div>
</div>
</div>
</div>
</div>
</body>
</html>CSS(css/style.css)
/* ==========================================
CSS変数とリセット
========================================== */
:root {
--primary: #667eea;
--secondary: #764ba2;
--accent: #f093fb;
--success: #4facfe;
--warning: #f6d365;
--danger: #ff6b6b;
--text: #333;
--text-light: #666;
--white: #ffffff;
--shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
--shadow-hover: 0 8px 30px rgba(0, 0, 0, 0.2);
--radius: 12px;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, var(--primary), var(--secondary));
min-height: 100vh;
padding: 2rem 1rem;
}
/* ==========================================
レイアウト
========================================== */
.container {
max-width: 1200px;
margin: 0 auto;
}
.page-title {
text-align: center;
color: var(--white);
font-size: 3rem;
margin-bottom: 3rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
/* ==========================================
基本カードスタイル
========================================== */
.card {
background: var(--white);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: var(--transition);
cursor: pointer;
height: 300px;
position: relative;
}
.card__content {
padding: 2rem;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.card__title {
font-size: 1.5rem;
margin-bottom: 1rem;
color: var(--text);
}
.card__text {
color: var(--text-light);
line-height: 1.6;
margin-bottom: 1rem;
}
.card__icon {
font-size: 3rem;
margin-top: 1rem;
}
.card__button {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: var(--white);
border: none;
padding: 0.75rem 1.5rem;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
transition: var(--transition);
}
.card__button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
/* ==========================================
フリップカード
========================================== */
.card--flip {
perspective: 1000px;
}
.card__inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.card--flip:hover .card__inner {
transform: rotateY(180deg);
}
.card__front,
.card__back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: var(--radius);
}
.card__back {
background: linear-gradient(135deg, var(--accent), var(--success));
color: var(--white);
transform: rotateY(180deg);
}
/* ==========================================
グロウカード
========================================== */
.card--glow {
position: relative;
}
.card--glow::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: var(--radius);
background: linear-gradient(45deg, var(--accent), var(--success), var(--warning));
z-index: -1;
opacity: 0;
transition: var(--transition);
animation: gradient-shift 3s ease infinite;
}
@keyframes gradient-shift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.card--glow:hover::before {
opacity: 1;
transform: scale(1.05);
filter: blur(20px);
}
/* ==========================================
スライドカード
========================================== */
.card--slide {
overflow: hidden;
}
.card__image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.card__image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: var(--transition);
}
.card--slide:hover .card__image img {
transform: scale(1.1);
}
.card--slide .card__content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
z-index: 2;
height: auto;
padding: 1.5rem;
transform: translateY(50%);
transition: var(--transition);
}
.card--slide:hover .card__content {
transform: translateY(0);
}
.card__overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(102, 126, 234, 0.9);
color: var(--white);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: var(--transition);
z-index: 3;
}
.card--slide:hover .card__overlay {
opacity: 1;
}
.card__overlay-content {
text-align: center;
transform: translateY(20px);
transition: var(--transition);
}
.card--slide:hover .card__overlay-content {
transform: translateY(0);
}
/* ==========================================
バウンスカード
========================================== */
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0) scale(1);
}
10% {
transform: translateY(-8px) scale(1.05);
}
40% {
transform: translateY(-20px) scale(1.1);
}
60% {
transform: translateY(-10px) scale(1.05);
}
}
.card--bounce:active {
animation: bounce 0.8s ease;
}
/* ==========================================
ローディングカード
========================================== */
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(102, 126, 234, 0.3);
border-top: 4px solid var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-top: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* ==========================================
3Dカード
========================================== */
.card--3d {
transform-style: preserve-3d;
transition: transform 0.6s;
}
.card--3d:hover {
transform: rotateX(10deg) rotateY(10deg) scale(1.05);
}
.card--3d .card__content::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, transparent, rgba(255, 255, 255, 0.1));
opacity: 0;
transition: var(--transition);
border-radius: var(--radius);
}
.card--3d:hover .card__content::before {
opacity: 1;
}
/* ==========================================
レスポンシブ
========================================== */
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
margin-bottom: 2rem;
}
.cards-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.card {
height: 250px;
}
.card__content {
padding: 1.5rem;
}
}
/* ==========================================
アクセシビリティ
========================================== */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* フォーカス表示 */
.card:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
}
.card__button:focus-visible {
outline: 2px solid var(--white);
outline-offset: 2px;
}📝 まとめ・質疑応答(5分)
アニメーションのパフォーマンス考慮点
✅ 最適化のポイント
-
GPUアクセラレーション対応プロパティを使用
transformopacityfilter
-
避けるべきプロパティ
width,heighttop,left,right,bottommargin,padding
-
will-changeの活用.element { will-change: transform; } -
アクセシビリティの考慮
@media (prefers-reduced-motion: reduce) { .animated { animation: none; } }
次回予告:JavaScript基礎
次回からプログラミング言語であるJavaScriptを学習します:
- 変数の宣言方法
- データ型の理解
- 制御構造(条件分岐・繰り返し)
- 関数の基礎
🏠 宿題
-
オリジナルボタンコレクションの作成
- 5種類以上のホバーエフェクト
- アクセシビリティに配慮した実装
-
ローディングアニメーションの作成
@keyframes pulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.7; } 100% { transform: scale(1); opacity: 1; } } -
CSS変数を使ったテーマシステム
- ライトテーマ・ダークテーマの切り替え
- アニメーション付きテーマ変更
📚 参考リソース
次回もお楽しみに! ✨
Last updated on