【CSS実践入門 #5】JavaScript入門 - インタラクティブなウェブサイトを作る

パソコ(ブログアシスタント) パソコ

パソコだよ!今日も一緒に学んでいこう!

JavaScript入門 - ウェブサイトに動きをつける魔法の言語

レスポンシブデザインを学んだ前回に続き、今回は「JavaScript」について詳しく学んでいきます。静的なHTMLとCSSに動的な機能を追加する重要な技術です。

🎯 今回学ぶこと

  • JavaScriptの基本構文と概念
  • DOM操作の基礎と実践
  • イベントハンドリングの実装方法
  • 実用的な機能の作成例
  • モダンJavaScriptのベストプラクティス

📚 JavaScriptとは?

JavaScriptは、ウェブページに動的な機能を追加するプログラミング言語です。HTML(構造)、CSS(見た目)に続く第3の要素として、「動作」を担当します。

3つの役割分担

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html>
<head>
    <style>
        /* CSS: 見た目を定義 */
        .button {
            background: #3498db;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        .button:hover {
            background: #2980b9;
        }
    </style>
</head>
<body>
    <!-- HTML: 構造を定義 -->
    <button class="button" onclick="showMessage()">クリックしてね</button>
    <div id="message"></div>

    <script>
        // JavaScript: 動作を定義
        function showMessage() {
            document.getElementById('message').innerHTML = 
                '<p style="color: green;">こんにちは、JavaScript!</p>';
        }
    </script>
</body>
</html>

🔤 JavaScript基本構文

変数宣言

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ES6以降の推奨書き方
let userName = "田中太郎";      // 変更可能な変数
const siteTitle = "技術ブログ";  // 変更不可な定数
var oldStyle = "古い書き方";     // 古い書き方(避ける)

// データ型
let number = 42;                 // 数値
let text = "文字列";             // 文字列
let isActive = true;             // 真偽値
let items = [1, 2, 3];          // 配列
let person = {                   // オブジェクト
    name: "山田花子",
    age: 25
};

関数定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 従来の関数定義
function greet(name) {
    return `こんにちは、${name}さん!`;
}

// アロー関数(ES6)
const greet2 = (name) => {
    return `こんにちは、${name}さん!`;
};

// 短縮形
const greet3 = name => `こんにちは、${name}さん!`;

// 使用例
console.log(greet("佐藤"));  // "こんにちは、佐藤さん!"

条件分岐・ループ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// if文
const score = 85;
if (score >= 90) {
    console.log("優秀!");
} else if (score >= 70) {
    console.log("良好");
} else {
    console.log("もう少し頑張ろう");
}

// for文
const colors = ["red", "blue", "green"];
for (let i = 0; i < colors.length; i++) {
    console.log(`色${i + 1}: ${colors[i]}`);
}

// forEach(配列専用・推奨)
colors.forEach((color, index) => {
    console.log(`色${index + 1}: ${color}`);
});

🌐 DOM操作の基礎

DOM(Document Object Model) は、HTMLドキュメントをJavaScriptで操作するためのインターフェースです。

要素の取得

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ID で取得
const title = document.getElementById('main-title');

// クラス名で取得(最初の要素)
const firstButton = document.querySelector('.button');

// クラス名で取得(すべての要素)
const allButtons = document.querySelectorAll('.button');

// タグ名で取得
const allParagraphs = document.getElementsByTagName('p');

console.log('タイトル:', title.textContent);
console.log('ボタン数:', allButtons.length);

要素の内容変更

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// テキスト内容の変更
const heading = document.getElementById('page-title');
heading.textContent = '新しいタイトル';

// HTML内容の変更
const container = document.getElementById('content');
container.innerHTML = `
    <h2>動的に生成されたコンテンツ</h2>
    <p>JavaScriptで作成しました!</p>
`;

// 属性の変更
const image = document.querySelector('.hero-image');
image.src = '/images/new-hero.jpg';
image.alt = '新しいヒーロー画像';

スタイルの変更

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const box = document.getElementById('color-box');

// 直接スタイル変更
box.style.backgroundColor = '#e74c3c';
box.style.color = 'white';
box.style.padding = '20px';
box.style.borderRadius = '10px';

// CSSクラスの追加・削除
box.classList.add('highlighted');
box.classList.remove('hidden');
box.classList.toggle('active'); // あれば削除、なければ追加

// クラスの存在確認
if (box.classList.contains('active')) {
    console.log('activeクラスが設定されています');
}

🖱️ イベントハンドリング

ユーザーの操作に応じてJavaScriptを実行する仕組みです。

基本的なイベント

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// クリックイベント
const button = document.getElementById('click-me');
button.addEventListener('click', function() {
    alert('ボタンがクリックされました!');
});

// アロー関数バージョン
button.addEventListener('click', () => {
    console.log('クリックされました');
});

// マウスオーバー・アウト
const hoverBox = document.querySelector('.hover-box');
hoverBox.addEventListener('mouseenter', () => {
    hoverBox.style.backgroundColor = '#3498db';
});

hoverBox.addEventListener('mouseleave', () => {
    hoverBox.style.backgroundColor = '#ecf0f1';
});

フォームイベント

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// フォーム送信
const form = document.getElementById('contact-form');
form.addEventListener('submit', (event) => {
    event.preventDefault(); // デフォルトの送信を阻止
    
    const name = document.getElementById('name').value;
    const email = document.getElementById('email').value;
    
    if (!name || !email) {
        alert('名前とメールアドレスを入力してください');
        return;
    }
    
    console.log('送信データ:', { name, email });
    // ここで実際の送信処理を行う
});

// 入力値の変更監視
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (event) => {
    const query = event.target.value;
    console.log('検索キーワード:', query);
    // リアルタイム検索の実装
});

🎨 実践例1:アコーディオンメニュー

よく使われるアコーディオンメニューを作成してみましょう。

HTML構造

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="accordion">
    <div class="accordion-item">
        <div class="accordion-header" data-target="content1">
            <h3>セクション1</h3>
            <span class="accordion-icon">+</span>
        </div>
        <div class="accordion-content" id="content1">
            <p>ここにコンテンツが入ります。</p>
            <p>複数の段落も表示できます。</p>
        </div>
    </div>
    
    <div class="accordion-item">
        <div class="accordion-header" data-target="content2">
            <h3>セクション2</h3>
            <span class="accordion-icon">+</span>
        </div>
        <div class="accordion-content" id="content2">
            <p>2番目のセクションの内容です。</p>
        </div>
    </div>
</div>

CSS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.accordion {
    max-width: 600px;
    margin: 20px auto;
}

.accordion-item {
    border: 1px solid #ddd;
    margin-bottom: 10px;
    border-radius: 8px;
    overflow: hidden;
}

.accordion-header {
    background: #f8f9fa;
    padding: 15px 20px;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    transition: background 0.3s ease;
}

.accordion-header:hover {
    background: #e9ecef;
}

.accordion-header h3 {
    margin: 0;
    color: #333;
}

.accordion-icon {
    font-size: 20px;
    font-weight: bold;
    color: #666;
    transition: transform 0.3s ease;
}

.accordion-content {
    padding: 0 20px;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease, padding 0.3s ease;
}

.accordion-content.active {
    max-height: 200px; /* 適切な高さに調整 */
    padding: 20px;
}

.accordion-header.active .accordion-icon {
    transform: rotate(45deg);
}

JavaScript実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class AccordionMenu {
    constructor(selector) {
        this.accordion = document.querySelector(selector);
        this.headers = this.accordion.querySelectorAll('.accordion-header');
        this.init();
    }

    init() {
        this.headers.forEach(header => {
            header.addEventListener('click', (e) => {
                this.toggleAccordion(e.currentTarget);
            });
        });
    }

    toggleAccordion(clickedHeader) {
        const targetId = clickedHeader.getAttribute('data-target');
        const targetContent = document.getElementById(targetId);
        const isActive = targetContent.classList.contains('active');

        // 他のアコーディオンを閉じる(オプション)
        this.closeAllAccordions();

        // クリックされたアコーディオンを開閉
        if (!isActive) {
            clickedHeader.classList.add('active');
            targetContent.classList.add('active');
        }
    }

    closeAllAccordions() {
        this.headers.forEach(header => {
            const targetId = header.getAttribute('data-target');
            const content = document.getElementById(targetId);
            
            header.classList.remove('active');
            content.classList.remove('active');
        });
    }
}

// 使用方法
document.addEventListener('DOMContentLoaded', () => {
    new AccordionMenu('.accordion');
});

🎨 実践例2:モーダルウィンドウ

ユーザーインターフェースでよく使われるモーダルウィンドウを作成します。

HTML構造

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- モーダルトリガー -->
<button class="open-modal" data-modal="modal1">
    モーダルを開く
</button>

<!-- モーダルウィンドウ -->
<div class="modal-overlay" id="modal1">
    <div class="modal">
        <div class="modal-header">
            <h2>お知らせ</h2>
            <button class="modal-close">&times;</button>
        </div>
        <div class="modal-body">
            <p>これはモーダルウィンドウの内容です。</p>
            <p>重要な情報を表示するのに便利です。</p>
        </div>
        <div class="modal-footer">
            <button class="btn btn-primary">OK</button>
            <button class="btn btn-secondary modal-close">キャンセル</button>
        </div>
    </div>
</div>

CSS

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
.modal-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    z-index: 1000;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.modal-overlay.active {
    display: flex;
    justify-content: center;
    align-items: center;
    opacity: 1;
}

.modal {
    background: white;
    border-radius: 8px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
    max-width: 500px;
    width: 90%;
    max-height: 90%;
    overflow: auto;
    transform: scale(0.8);
    transition: transform 0.3s ease;
}

.modal-overlay.active .modal {
    transform: scale(1);
}

.modal-header {
    padding: 20px;
    border-bottom: 1px solid #eee;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.modal-header h2 {
    margin: 0;
    color: #333;
}

.modal-close {
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    color: #999;
    padding: 0;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.modal-close:hover {
    color: #333;
}

.modal-body {
    padding: 20px;
    color: #666;
    line-height: 1.6;
}

.modal-footer {
    padding: 20px;
    border-top: 1px solid #eee;
    display: flex;
    gap: 10px;
    justify-content: flex-end;
}

.btn {
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: 500;
    transition: background 0.2s ease;
}

.btn-primary {
    background: #3498db;
    color: white;
}

.btn-primary:hover {
    background: #2980b9;
}

.btn-secondary {
    background: #95a5a6;
    color: white;
}

.btn-secondary:hover {
    background: #7f8c8d;
}

JavaScript実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class ModalManager {
    constructor() {
        this.modals = document.querySelectorAll('.modal-overlay');
        this.openButtons = document.querySelectorAll('.open-modal');
        this.closeButtons = document.querySelectorAll('.modal-close');
        
        this.init();
    }

    init() {
        // 開くボタンのイベント
        this.openButtons.forEach(button => {
            button.addEventListener('click', (e) => {
                const modalId = e.currentTarget.getAttribute('data-modal');
                this.openModal(modalId);
            });
        });

        // 閉じるボタンのイベント
        this.closeButtons.forEach(button => {
            button.addEventListener('click', (e) => {
                const modal = e.currentTarget.closest('.modal-overlay');
                this.closeModal(modal.id);
            });
        });

        // オーバーレイクリックで閉じる
        this.modals.forEach(modal => {
            modal.addEventListener('click', (e) => {
                if (e.target === modal) {
                    this.closeModal(modal.id);
                }
            });
        });

        // Escapeキーで閉じる
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                this.closeAllModals();
            }
        });
    }

    openModal(modalId) {
        const modal = document.getElementById(modalId);
        if (modal) {
            modal.classList.add('active');
            document.body.style.overflow = 'hidden'; // スクロール防止
        }
    }

    closeModal(modalId) {
        const modal = document.getElementById(modalId);
        if (modal) {
            modal.classList.remove('active');
            document.body.style.overflow = ''; // スクロール復元
        }
    }

    closeAllModals() {
        this.modals.forEach(modal => {
            modal.classList.remove('active');
        });
        document.body.style.overflow = '';
    }
}

// 使用方法
document.addEventListener('DOMContentLoaded', () => {
    new ModalManager();
});

🔄 非同期処理とAPI連携

現代のWebアプリケーションに欠かせない非同期処理について学びましょう。

Fetch API の基本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// GET リクエスト
async function fetchUserData(userId) {
    try {
        const response = await fetch(`https://api.example.com/users/${userId}`);
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const userData = await response.json();
        return userData;
    } catch (error) {
        console.error('データ取得エラー:', error);
        return null;
    }
}

// POST リクエスト
async function createUser(userData) {
    try {
        const response = await fetch('https://api.example.com/users', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(userData)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const result = await response.json();
        console.log('ユーザー作成成功:', result);
        return result;
    } catch (error) {
        console.error('ユーザー作成エラー:', error);
        return null;
    }
}

// 使用例
async function loadAndDisplayUser(userId) {
    const loadingElement = document.getElementById('loading');
    const userElement = document.getElementById('user-info');
    
    // ローディング表示
    loadingElement.style.display = 'block';
    userElement.innerHTML = '';
    
    const user = await fetchUserData(userId);
    
    // ローディング非表示
    loadingElement.style.display = 'none';
    
    if (user) {
        userElement.innerHTML = `
            <h3>${user.name}</h3>
            <p>Email: ${user.email}</p>
            <p>会社: ${user.company?.name || '未設定'}</p>
        `;
    } else {
        userElement.innerHTML = '<p>ユーザー情報の読み込みに失敗しました。</p>';
    }
}

🛠️ 実用的なユーティリティ関数

実際の開発でよく使う便利な関数を紹介します。

デバウンス(連続実行制御)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function debounce(func, delay) {
    let timeoutId;
    return function (...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
}

// 使用例:検索入力の最適化
const searchInput = document.getElementById('search');
const performSearch = debounce((query) => {
    console.log('検索実行:', query);
    // 実際の検索処理
}, 300);

searchInput.addEventListener('input', (e) => {
    performSearch(e.target.value);
});

スロットリング(実行頻度制御)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function throttle(func, limit) {
    let inThrottle;
    return function (...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// 使用例:スクロールイベントの最適化
const handleScroll = throttle(() => {
    const scrolled = window.scrollY;
    console.log('スクロール位置:', scrolled);
    
    // ヘッダーの表示/非表示制御など
    const header = document.querySelector('.site-header');
    if (scrolled > 100) {
        header.classList.add('scrolled');
    } else {
        header.classList.remove('scrolled');
    }
}, 100);

window.addEventListener('scroll', handleScroll);

ローカルストレージ管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class StorageManager {
    static save(key, data) {
        try {
            const serialized = JSON.stringify(data);
            localStorage.setItem(key, serialized);
            return true;
        } catch (error) {
            console.error('保存エラー:', error);
            return false;
        }
    }

    static load(key, defaultValue = null) {
        try {
            const item = localStorage.getItem(key);
            return item ? JSON.parse(item) : defaultValue;
        } catch (error) {
            console.error('読み込みエラー:', error);
            return defaultValue;
        }
    }

    static remove(key) {
        try {
            localStorage.removeItem(key);
            return true;
        } catch (error) {
            console.error('削除エラー:', error);
            return false;
        }
    }

    static clear() {
        try {
            localStorage.clear();
            return true;
        } catch (error) {
            console.error('クリアエラー:', error);
            return false;
        }
    }
}

// 使用例
const userPreferences = {
    theme: 'dark',
    language: 'ja',
    notifications: true
};

// 設定保存
StorageManager.save('userPrefs', userPreferences);

// 設定読み込み
const loadedPrefs = StorageManager.load('userPrefs', {
    theme: 'light',
    language: 'en',
    notifications: false
});

console.log('ユーザー設定:', loadedPrefs);

🚀 次回予告

次回は「JavaScript検索エンジン実装」について学びます:

  • 高速検索アルゴリズムの実装
  • スコアリングシステムの構築
  • ハイライト機能の追加
  • パフォーマンス最適化技術
  • 実際のサイトでの導入方法

📝 今回のまとめ

  1. JavaScriptはウェブページに動的な機能を追加する言語
  2. DOM操作で HTML要素を動的に変更可能
  3. イベントハンドリングでユーザー操作に応答
  4. 非同期処理でAPIとの連携が可能
  5. 実用的な機能(アコーディオン、モーダルなど)の実装方法

JavaScriptをマスターすれば、ユーザーにとって使いやすく魅力的なウェブサイトを作ることができるようになります!


💡 質問やフィードバックがあれば、コメント欄やTwitter(@firebird19245)でお気軽にどうぞ!

シリーズ記事

  • 【第1回】CSSの基礎知識
  • 【第2回】CSSセレクタとカスケード
  • 【第3回】Flexboxレイアウト入門
  • 【第4回】レスポンシブデザイン完全ガイド
  • 【第5回】JavaScript入門 - インタラクティブなウェブサイト作り(この記事)
  • 【第6回】JavaScript検索エンジン実装(次回)
  • 【第7回】アニメーション・トランジション実装
この記事をシェアX Facebook はてブ
技術ネタ、趣味や備忘録などを書いているブログです
Hugo で構築されています。
テーマ StackJimmy によって設計されています。