Open2

お勉強3週目

nguyennguyen

今日のサマリー

  • お勉強1 ~ 2週目で写経した練習問題4をベースに、JavaScript: 操作の追加 を参考にした練習問題5の作成をClaudeに依頼
  • 学習コストが少し高そう (内容が難しい...) だったので、基本的なインタラクティブな機能を3つのみに絞って追加した
  • 練習問題5の写経、ブラウザでの確認を行なった
  • AIがmdnページから参孝にした点を確認した
  • 機能追加に伴い、stylebodyscriptを追加の3つが必要なことを知った (大変...)

今日やったこと

1. 練習問題5を写経する

  • 追加した3つのJavaScript機能
- テーマ切り替え(ダークモード)
- 名前入力と挨拶表示
- スキルバーのクリックアニメーション
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width", initial-scale="1.0">
    <title>グエン福康のポートフォリオ - レスポンシブ版</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: "Hiragino Sans", "Meiryo", sans-serif;
            line-height: 1.7;
            color: #2c3e50;
            background-color: #f7f8fa;
        }

        /* ダークモード用のスタイル */
        body.dark-mode {
            background-color: #1a1a2e;
            color: #e8e8e8;
        }

        body.dark-mode .card{
            background: #16213e;
            color: #e8e8e8;
        }

        body.dark-mode header{
            background: linear-gradient(135deg, #4a5fc1 0%, #6b4199 100%);
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }

        /* テーマ切り替えボタン */
        .theme-button {
            position: fixed;
            top: 20px;
            right: 20px;
            background: white;
            border: none;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            font-size: 24px;
            cursor: pointer;
            box-shadow: 0.4px 15px rgba(0,0,0,0.1);
            transition: transform 0.3s ease;
            z-index: 100;
        }

        .theme-button:hover {
            transform: scale(1.1);
        }

        header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 80px 20px;
            text-align: center;
            border-radius: 15px;
            margin-bottom: 40px;
            position: relative;
            overflow: hidden;
        }

        header::before {
            content: "";
            position: absolute;
            top: -50%;
            right: -50%;
            width: 200%;
            height: 200%;
            background: rgba(255, 255, 255, 0.1);
            transform: rotate(45deg);
        }

        header h1 {
            font-size: 3em;
            margin-bottom: 15px;
            position: relative;
            z-index: 1;
        }

        .tagline {
            font-size: 1.3em;
            opacity: 0.95;
            position: relative;
            z-index: 1;
        }

        /* 名前入力セクション */
        .greeting-section {
            background: white;
            padding: 30px;
            border-radius: 15px;
            box-shadow: 0.4px 15px rgba(0,0,0,0.08);
            margin-bottom: 30px;
            text-align: center;
            transition: background 0.3s ease;
        }

        body.dark-mode .greeting-section{
            background: #16213e;
        }

        .greeting-section h2 {
            color: #667eea;
            margin-bottom: 20px;
        }

        #nameInput {
            padding: 10px 15px;
            border: 2px solid #e8e8e8;
            border-radius: 8px;
            font-size: 16px;
            width: 250px;
            margin-right: 10px;
        }

        #nameInput:focus {
            outline: none;
            border-color: #667eea;
        }

        #greetButton {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 10px 25px;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            cursor: pointer;
            transition: transform 0.3s ease;
        }

        #greeButton:hover {
            transform: translateY(-2px);
        }

        #greetingMessage {
            margin-top: 20px;
            font-size: 1.2em;
            color: #667eea;
            font-weight: bold;
        }

        .content-grid {
            display: grid;
            grid-template-columns: 2fr 1fr;
            gap: 30px;
            margin-bottom: 30px;
        }

        .main-content {
            display: flex;
            flex-direction: column;
            gap: 30px;
        }

        .sidebar {
            display: flex;
            flex-direction: column;
            gap: 30px;
        }

        .card {
            background: white;
            padding: 35px;
            border-radius: 15px;
            box-shadow: 0.4px 15px rgba(0,0,0,0.08);
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }

        .card:hover {
            transform:  translateY(-5px);
            box-shadow: 0.6px 20px rgba(0,0,0,0.12);
        }

        .card h2 {
            color: #667eea;
            margin-bottom: 20px;
            font-size: 1.8em;
            border-bottom: 2px solid #e8e8e8;
            padding-bottom: 10px;
        }

        .progress-bar {
            background-color: #e8e8e8;
            height: 10px;
            border-radius: 5px;
            overflow: hidden;
            margin: 10px 0;
        }

        .progress-bar:hover::after {
            content: "クリックしてアニメーション";
            position: absolute;
            top: -25px;
            left: 50%;
            transform: translateX(-50%);
            background: #333;
            color: white;
            padding: 3px 10px;
            border-radius: 3px;
            font-size: 12px;
            white-space: nowrap;
        }

        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #667eea, #764ba2);
            border-radius: 5px;
            transition: width 1s ease;
        }

        .skill-item {
            margin-bottom: 20px;
        }

        .skill-header {
            display: flex;
            justify-content: space-between;
            margin-bottom: 5px;
        }

        .timeline {
            position: relative;
            padding-left: 30px;
        }

        .timeline::before {
            content: "";
            position: absolute;
            left: 8px;
            top: 0;
            bottom: 0;
            width: 2px;
            background: #667eea;
        }

        .timeline-item {
            position: relative;
            margin-bottom: 25px;
        }

        .timeline-item::before {
            content: "";
            position: absolute;
            left: -26px;
            top: 5px;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #667eea;
            border: 3px solid white;
            box-shadow: 0.2px 5px rgba(0,0,0,0.2);
        }

        .timeline-date {
            color: #667eea;
            font-weight: bold;
            margin-bottom: 5px;
        }

        footer {
            background: #2c3e50;
            color: white;
            text-align: center;
            padding: 30px;
            border-radius: 15px;
        }

        .social-links {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-top: 20px;
        }

        .social-link {
            display: inline-block;
            width: 40px;
            height: 40px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 50%;
            line-height: 40px;
            color: white;
            text-decoration: none;
            transition: background 0.3s sase;
        }

        .social-link:hover {
            background: #667eea;
        }

        /* タブレット対応 */
        @media (max-width: 768px) {
            header h1 {
                font-size: 2em;
            }

            .content-grid {
                grid-template-columns: 2em;
            }

            .card {
                padding: 25px;
            }
        }

        /* スマートフォン対応 */
        @media (max-width: 480px) {
            header {
                padding: 50px 15px;
            }

        header h1 {
            font-size: 1em;
        }

        .tagline {
            font-size: 1em;
        }

        .card {
            padding: 20px;
        }

        .card h2 {
            font-size: 1.4em;
        }

    }
    </style>
</head>
<body>
    <!-- 機能1: テーマ切り替えボタン -->
    <button class="theme-button" onclick="toggleTheme()">🌙</button>

    <div class="container">
        <header>
            <h1> グエン福康のポートフォリオ</h1>
            <p class="tagline">一人前のエンジニアを目指して</p>
        </header>

        <!-- 機能2: 名前入力と挨拶表示 -->
        <div class="greeting-section">
            <h2>訪問者の方へ</h2>
            <p>あなたのお名前を教えて下さい</p>
            <div>
                <input type="text" id="nameInput" placeholder="お名前を入力">
                <button id="greetButton" onclick="showGreeting()">送信</button>
            </div>
            <div id="greetingMessage"></div>
        </div>

        <div class="content-grid">
            <div class="main-content">
                <div class="card">    
                    <h2>自己紹介</h2>
                    <p>
                        こんにちは。グエンと申します。現在Web開発の世界に足を踏み入れたばかりの初心者エンジニアです。
                        毎日少しずつですが、コードを書く練習をしています。
                    </p>
                    <p>
                        将来的に自分でプロダクトを作るために、プログラミングを学習中です。
                        この自己紹介ページは、学習の一環として作成しています。
                    </p>
                </div>
                
                <div class="card">
                    <h2>スキルセット</h2>
                    <p style="margin-bottom: 20px; color: #999;"> プログレスバーをクリックするとアニメーションします</p>

                    <!-- 機能3: クリックでアニメーションするスキルバー -->
                    <div class="skill-item">
                        <div class="skill-header">
                            <span>HTML</span>
                            <span>70%</span>
                        </div>
                        <div class="progress-bar" onclick="animateSkillBar(this, 70)">
                            <div class="progress-fill" style="width: 70%;"></div>
                        </div>
                    </div>
                    <div class="skill-item">
                        <div class="skill-header">
                            <span>CSS</span>
                            <span>60%</span>
                        </div>
                        <div class="progress-bar" onclick="animateSkillBar(this, 60)">
                            <div class="progress-fill" style="width:  60%;"></div>
                        </div>
                    </div>
                    <div class="skill-item">
                        <div class="skill-header">
                            <span>JavaScript</span>
                            <span>30%</span>
                        </div>
                        <div class="progress-bar" onclick="animateSkillBar(this, 30)">
                            <div class="progress-fill" style="width: 30%;"></div>
                        </div>
                    </div>
                    <div class="skill-item">
                        <div class="skill-header">
                            <span>Git</span>
                            <span>40%</span>
                        </div>
                        <div class="progress-bar" onclick="animateSkillBar(this, 40)">
                            <div class="progress-fill" style="width: 40%;"></div>
                        </div>
                    </div>
                </div>
            </div>
            
            <div class="sidebar">
                <div class="card">
                    <h2>学習計画</h2>
                    <div class="timeline">
                        <div class="timeline-item">
                            <div class="timeline-date">現在</div>
                            <div>HTML/CSS基礎</div>
                        </div>
                        <div class="timeline-item">
                            <div class="timeline-date">1ヶ月後</div>
                            <div>JavaScript入門</div>
                        </div>
                        <div class="timeline-item">
                            <div class="timeline-date">3ヶ月後</div>
                            <div>React基礎</div>
                        </div>
                        <div class="timeline-item">
                            <div class="timeline-date">6ヶ月後</div>
                            <div>フルスタック開発</div>
                        </div>
                    </div>
                </div>
                
                <div class="card">
                    <h2>趣味</h2>
                    <p>📚読書</p>
                    <p>🎬映画鑑賞</p>
                    <p>☕️カフェ巡り</p>
                    <p>💻プログラミング学習</p>
                </div>
            </div>
        </div>
        
        <footer>
            <h2>Contact</h2>
            <p>お気軽にご連絡ください</p>
            <div class="social-links">
                <a href="#" class="social-link">📧</a>
                <a href="#" class="social-link">💼</a>
                <a href="#" class="social-link">🐦</a>
            </div>
        </footer>
    </div>

    <script>
        // 機能1: テーマ(明暗)を切り替える関数
        function toggleTheme() {
            // body要素を取得
            const body = document.body;
            // ボタンを取得
            const button = document.querySelector('.theme-button');

            // dark-modeクラスがあるかチェック
            if (body.classList.contains('dark-mode')) {
                // ダークモードを解除
                body.classList.remove('dark-mode');
                // ボタンの絵文字を月に変更
                button.textContent = '🌙';
            } else {
                // ダークモードを適用
                body.classList.add('dark-mode');
                // ボタンの絵文字を太陽に変更
                button.textContent = '☀️';
            }
        }

        // 機能2: 名前を入力して挨拶を表示する関数
        function showGreeting() {
            // 入力欄の要素を取得
            const nameInput = document.getElementById('nameInput');
            // 挨拶を表示する場所の要素を取得
            const greetingMessage = document.getElementById('greetingMessage');

            // 入力された名前を取得(前後の空白を削除)
            const name = nameInput.value.trim();

            // 名前が入力されているかチェック
            if (name === '') {
                // 名前が空の場合、メッセージを表示
                greetingMessage.textContent = 'お名前を入力してください';
            } else {
                // 名前が入力されている場合、挨拶を表示
                greetingMessage.textContent = 'こんにちは、' + name + 'さん!私のポートフォリオへようこそ!';
            }
        }

        // 機能3: スキルバーをアニメーションする関数
        function animateSkillBar(progressBar, targetPercent) {
            // プログレスバー内の塗りつぶし部分を取得
            const progressFill = progressBar.querySelector('.progress-fill');

            // 一度0%にリセット
            progressFill.style.width = '0%';

            // 少し待ってから、目標の0%まで伸ばす
            setTimeout(function() {
                progressFill.style.width = targetPercent + '%';
            }, 100);
        }
    </script>
</body>
</html>

2. 練習問題4との差分を確認

  • style: 追加機能毎にstyleを追加
  • body: 切り替えボタン/名前入力欄/アニメーションするスキルバーの追加
  • script: 追加機能毎の関数を追加

3. ブラウザで見てみる

  • file:///Users/nguyen/Desktop/my-portfolio/practice5/index.html

4. mdnページから参孝にした点を確認

- <script src="scripts/main.js"></script> を </body>タグの直前に配置
- querySelector() を使った要素の取得
- textContent プロパティでテキストを変更

機能1: テーマ切り替え(ダークモード)

mdnページには直接ない オリジナルの追加機能のようだが、以下の概念を応用している。

  • 条件文の例:if...else 文
  • classList の操作(DOM APIの応用)

機能2: 名前入力と挨拶表示

mdnの「パーソナライズされた挨拶メッセージの追加」セクションを簡略化しているそう。

// mdnの例
const myName = prompt("あなたの名前を入力してください。");
myHeading.textContent = `Mozilla はかっこいいよ、${myName} さん`;

// 練習問題5の例
const name = nameInput.value.trim();
greetingMessage.textContent = 'こんにちは、' + name + 'さん!';

機能3: スキルバーのクリックアニメーション

mdnの「画像の切り替えの追加」セクションの概念を応用しているそう。

// mdnの例:クリックで画像を切り替え
myImage.onclick = () => {
    // src属性を変更
};

// 練習問題5の例:クリックでスキルバーをアニメーション
function animateSkillBar(progressBar, targetPercent) {
    // widthスタイルを変更
}