プログラミング初心者がJavaScriptで最初に作るべき5つのアプリ
JavaScript初心者が学習効果を最大化できる5つのアプリを厳選。具体的なコード例と段階的な実装方法で、初心者でも確実に作れるアプリを詳しく解説します。
プログラミング初心者がJavaScriptで最初に作るべき5つのアプリ
みなさん、JavaScriptの基本を学んだけれど「何を作ればいいか分からない」と悩んでいませんか?
「教科書の内容は理解したけど、実際のアプリが作れない」 「どこから手をつけていいか分からない」
こんな気持ちになったことはありませんか?
この記事では、初心者が最初に作るべき5つのアプリを厳選しました。 それぞれ異なるスキルが身につくよう設計されており、段階的にレベルアップできる構成になっています。
完全なコードとともに実装方法を詳しく解説するので、ぜひ一緒に作ってみましょう!
なぜアプリ作りが重要なの?
理論だけでは身につかないスキル
プログラミングは実際にコードを書かないと上達しません。 単純にコードを読むだけでは得られない効果があります。
- 実践的スキルの習得
- エラー対処法の体験
- 完成の達成感による学習継続
- ポートフォリオとしての活用
実際に手を動かすことで、本当の実力が身につきます。
初心者向けアプリの条件
今回紹介するアプリは以下の条件で選びました。
- 30分〜2時間で完成できる分量
- 基本文法だけで実装可能
- 目に見える成果が得られる
- 段階的にスキルアップできる構成
- 実用性がある内容
順番に作ることで、確実にレベルアップできます。
アプリ1: 数当てゲーム(基礎固め編)
身につくスキル
まずは基本的なスキルを固めましょう。
- 変数と定数の使い分け
- 関数の定義と呼び出し
- 条件分岐(if-else)
- ループ処理
- ランダム数値生成
- 入力値の検証
これらがすべて一つのアプリで学べます。
完成コード
数当てゲームの全体像を見てみましょう。
class NumberGuessingGame { constructor() { this.targetNumber = Math.floor(Math.random() * 100) + 1; this.attempts = 0; this.maxAttempts = 7; this.gameStarted = false; } startGame() { this.gameStarted = true; console.log('=== 数当てゲーム ==='); console.log('1から100までの数字を当ててください'); console.log(`挑戦回数: ${this.maxAttempts}回`); console.log('頑張って!'); } makeGuess(userGuess) { // 入力値の検証 if (!this.gameStarted) { return 'ゲームが開始されていません。startGame()を実行してください。'; } if (typeof userGuess !== 'number' || userGuess < 1 || userGuess > 100) { return '1から100までの数字を入力してください。'; } this.attempts++; // 正解判定 if (userGuess === this.targetNumber) { const message = `🎉 正解! 答えは${this.targetNumber}でした!${this.attempts}回目で当たりました!`; this.resetGame(); return message; } // 残り回数チェック if (this.attempts >= this.maxAttempts) { const message = `😢 ゲームオーバー! 正解は${this.targetNumber}でした。またチャレンジしてみてください!`; this.resetGame(); return message; } // ヒント提供 const hint = userGuess > this.targetNumber ? 'もっと小さい数字です' : 'もっと大きい数字です'; const remainingAttempts = this.maxAttempts - this.attempts; return `${hint}! 残り${remainingAttempts}回`; } resetGame() { this.targetNumber = Math.floor(Math.random() * 100) + 1; this.attempts = 0; this.gameStarted = false; } getHint() { if (!this.gameStarted) { return 'ゲームが開始されていません。'; } const range = 10; const min = Math.max(1, this.targetNumber - range); const max = Math.min(100, this.targetNumber + range); return `ヒント: ${min}から${max}の間の数字です`; }}
// 使用例const game = new NumberGuessingGame();game.startGame();
console.log(game.makeGuess(50)); // "もっと小さい数字です! 残り6回"console.log(game.makeGuess(25)); // "もっと大きい数字です! 残り5回"console.log(game.getHint()); // "ヒント: 31から41の間の数字です"console.log(game.makeGuess(35)); // プレイを続ける...
クラスを使ってゲームの状態を管理しています。
constructor
でゲームの初期設定を行い、各メソッドで機能を分けています。
コードの詳細解説
重要な部分を一つずつ見ていきましょう。
// ランダム数値生成this.targetNumber = Math.floor(Math.random() * 100) + 1;
Math.random()
は0以上1未満の小数を返します。
100を掛けてMath.floor()
で整数にし、1を足すことで1〜100の数字を作ります。
// 入力値の検証if (typeof userGuess !== 'number' || userGuess < 1 || userGuess > 100) { return '1から100までの数字を入力してください。';}
入力値が数値かどうか、範囲内かどうかをチェックしています。 このような検証は、安全なプログラムを作るために重要です。
// 三項演算子を使ったヒントconst hint = userGuess > this.targetNumber ? 'もっと小さい数字です' : 'もっと大きい数字です';
条件によってメッセージを変える簡潔な書き方です。
このアプリを作ることで、JavaScriptの基本がしっかり身につきます。
アプリ2: ToDoリスト(DOM操作編)
身につくスキル
次はブラウザでの操作を学びましょう。
- HTML要素の取得と操作
- イベントリスナーの設定
- 動的なHTML生成
- ローカルストレージ活用
- フォーム処理
- CSS class操作
実際のWebアプリ開発で必要なスキルが学べます。
HTML構造
まずはHTMLを作成します。
<!DOCTYPE html><html lang="ja"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ToDoリスト</title> <style> body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; } .todo-container { background: #f9f9f9; padding: 20px; border-radius: 8px; } .todo-input { width: 70%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; } .add-btn { width: 25%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } .todo-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; margin: 5px 0; background: white; border-radius: 4px; } .todo-item.completed { text-decoration: line-through; opacity: 0.6; } .delete-btn { background: #dc3545; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; } .stats { margin-top: 20px; text-align: center; color: #666; } </style></head><body> <div class="todo-container"> <h1>ToDoリスト</h1> <div class="input-section"> <input type="text" class="todo-input" placeholder="新しいタスクを入力..." maxlength="100"> <button class="add-btn">追加</button> </div> <div class="todo-list"></div> <div class="stats"> <p>総タスク数: <span id="total-count">0</span> | 完了: <span id="completed-count">0</span> | 残り: <span id="remaining-count">0</span></p> </div> <div class="actions"> <button id="clear-completed">完了済みを削除</button> <button id="clear-all">全て削除</button> </div> </div> <script src="todo-app.js"></script></body></html>
シンプルですが、必要な要素がすべて含まれています。
JavaScript実装
次にJavaScriptでToDoリストの機能を実装します。
class TodoApp { constructor() { this.todos = this.loadTodos(); this.nextId = this.getNextId(); this.initializeElements(); this.attachEventListeners(); this.renderTodos(); this.updateStats(); } initializeElements() { this.todoInput = document.querySelector('.todo-input'); this.addBtn = document.querySelector('.add-btn'); this.todoList = document.querySelector('.todo-list'); this.totalCount = document.getElementById('total-count'); this.completedCount = document.getElementById('completed-count'); this.remainingCount = document.getElementById('remaining-count'); this.clearCompletedBtn = document.getElementById('clear-completed'); this.clearAllBtn = document.getElementById('clear-all'); } attachEventListeners() { // タスク追加 this.addBtn.addEventListener('click', () => this.addTodo()); this.todoInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.addTodo(); } }); // 一括操作 this.clearCompletedBtn.addEventListener('click', () => this.clearCompleted()); this.clearAllBtn.addEventListener('click', () => this.clearAll()); } addTodo() { const text = this.todoInput.value.trim(); if (text === '') { alert('タスクを入力してください'); return; } if (text.length > 100) { alert('タスクは100文字以内で入力してください'); return; } const todo = { id: this.nextId++, text: text, completed: false, createdAt: new Date().toISOString() }; this.todos.push(todo); this.todoInput.value = ''; this.saveTodos(); this.renderTodos(); this.updateStats(); } toggleTodo(id) { const todo = this.todos.find(todo => todo.id === id); if (todo) { todo.completed = !todo.completed; this.saveTodos(); this.renderTodos(); this.updateStats(); } } deleteTodo(id) { if (confirm('このタスクを削除しますか?')) { this.todos = this.todos.filter(todo => todo.id !== id); this.saveTodos(); this.renderTodos(); this.updateStats(); } } renderTodos() { this.todoList.innerHTML = ''; if (this.todos.length === 0) { this.todoList.innerHTML = '<p style="text-align: center; color: #888; padding: 20px;">タスクがありません</p>'; return; } this.todos.forEach(todo => { const todoElement = this.createTodoElement(todo); this.todoList.appendChild(todoElement); }); } createTodoElement(todo) { const div = document.createElement('div'); div.className = `todo-item ${todo.completed ? 'completed' : ''}`; div.innerHTML = ` <div style="display: flex; align-items: center; flex-grow: 1;"> <input type="checkbox" ${todo.completed ? 'checked' : ''} style="margin-right: 10px;"> <span style="flex-grow: 1; cursor: pointer;">${this.escapeHtml(todo.text)}</span> </div> <div> <button class="edit-btn" style="background: #28a745; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; margin-right: 5px;">編集</button> <button class="delete-btn">削除</button> </div> `; // イベントリスナー追加 const checkbox = div.querySelector('input[type="checkbox"]'); const editBtn = div.querySelector('.edit-btn'); const deleteBtn = div.querySelector('.delete-btn'); checkbox.addEventListener('change', () => this.toggleTodo(todo.id)); editBtn.addEventListener('click', () => this.editTodo(todo.id)); deleteBtn.addEventListener('click', () => this.deleteTodo(todo.id)); return div; } updateStats() { const total = this.todos.length; const completed = this.todos.filter(todo => todo.completed).length; const remaining = total - completed; this.totalCount.textContent = total; this.completedCount.textContent = completed; this.remainingCount.textContent = remaining; } // ローカルストレージ操作 saveTodos() { localStorage.setItem('todos', JSON.stringify(this.todos)); localStorage.setItem('nextId', this.nextId.toString()); } loadTodos() { const saved = localStorage.getItem('todos'); return saved ? JSON.parse(saved) : []; } getNextId() { const saved = localStorage.getItem('nextId'); return saved ? parseInt(saved) : 1; } // セキュリティ対策 escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }}
// アプリケーション開始document.addEventListener('DOMContentLoaded', () => { new TodoApp();});
コードの詳細解説
重要な部分を詳しく見てみましょう。
// DOM要素の取得this.todoInput = document.querySelector('.todo-input');
querySelector
でCSSセレクタを使って要素を取得します。
// イベントリスナーの設定this.addBtn.addEventListener('click', () => this.addTodo());
ボタンがクリックされたときにaddTodo
メソッドを実行します。
// 動的なHTML生成div.innerHTML = `...`;
テンプレートリテラルを使ってHTMLを動的に作成しています。
// ローカルストレージへの保存localStorage.setItem('todos', JSON.stringify(this.todos));
ブラウザを閉じてもデータが残るように保存しています。
このアプリで、実際のWebアプリ開発の基本が身につきます。
アプリ3: 電卓(計算ロジック編)
身につくスキル
次は計算処理とエラー処理を学びましょう。
- 数値演算処理
- 文字列操作
- 演算子の優先順位
- エラーハンドリング
- 状態管理
- UI/UX設計
複雑な計算ロジックの実装方法が学べます。
HTML構造
電卓のレイアウトを作成します。
<!DOCTYPE html><html lang="ja"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>電卓アプリ</title> <style> body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #f0f0f0; } .calculator { background: #333; padding: 20px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } .display { width: 100%; height: 80px; font-size: 24px; text-align: right; padding: 0 15px; border: none; background: #222; color: white; border-radius: 5px; margin-bottom: 15px; } .buttons { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; } button { height: 60px; font-size: 18px; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.2s; } .number, .decimal { background: #666; color: white; } .operator { background: #ff9500; color: white; } .equals { background: #ff9500; color: white; grid-column: span 2; } .clear { background: #a6a6a6; color: black; } .zero { grid-column: span 2; } button:hover { opacity: 0.8; } button:active { transform: scale(0.95); } </style></head><body> <div class="calculator"> <input type="text" class="display" readonly> <div class="buttons"> <button class="clear">AC</button> <button class="clear">CE</button> <button class="operator">%</button> <button class="operator">÷</button> <button class="number">7</button> <button class="number">8</button> <button class="number">9</button> <button class="operator">×</button> <button class="number">4</button> <button class="number">5</button> <button class="number">6</button> <button class="operator">-</button> <button class="number">1</button> <button class="number">2</button> <button class="number">3</button> <button class="operator">+</button> <button class="number zero">0</button> <button class="decimal">.</button> <button class="equals">=</button> </div> </div> <script src="calculator.js"></script></body></html>
本格的な電卓のようなレイアウトです。
JavaScript実装
電卓の機能を実装します。
class Calculator { constructor() { this.display = document.querySelector('.display'); this.buttons = document.querySelector('.buttons'); this.currentValue = '0'; this.previousValue = null; this.operator = null; this.waitingForOperand = false; this.initializeEventListeners(); this.updateDisplay(); } initializeEventListeners() { this.buttons.addEventListener('click', (e) => { if (!e.target.matches('button')) return; const target = e.target; const value = target.textContent; if (target.classList.contains('number')) { this.inputNumber(value); } else if (target.classList.contains('decimal')) { this.inputDecimal(); } else if (target.classList.contains('operator')) { this.inputOperator(value); } else if (target.classList.contains('equals')) { this.calculate(); } else if (target.classList.contains('clear')) { if (value === 'AC') { this.allClear(); } else if (value === 'CE') { this.clearEntry(); } } }); // キーボード操作対応 document.addEventListener('keydown', (e) => { this.handleKeyboardInput(e); }); } inputNumber(num) { if (this.waitingForOperand) { this.currentValue = num; this.waitingForOperand = false; } else { this.currentValue = this.currentValue === '0' ? num : this.currentValue + num; } this.updateDisplay(); } inputDecimal() { if (this.waitingForOperand) { this.currentValue = '0.'; this.waitingForOperand = false; } else if (this.currentValue.indexOf('.') === -1) { this.currentValue += '.'; } this.updateDisplay(); } inputOperator(nextOperator) { const inputValue = parseFloat(this.currentValue); if (this.previousValue === null) { this.previousValue = inputValue; } else if (this.operator) { const currentValue = this.previousValue || 0; const newValue = this.performCalculation(this.operator, currentValue, inputValue); if (newValue === null) return; // エラーの場合は操作を中断 this.currentValue = String(newValue); this.previousValue = newValue; this.updateDisplay(); } this.waitingForOperand = true; this.operator = nextOperator; } calculate() { const inputValue = parseFloat(this.currentValue); if (this.previousValue === null || this.operator === null) { return; } const newValue = this.performCalculation(this.operator, this.previousValue, inputValue); if (newValue === null) return; // エラーの場合は操作を中断 this.currentValue = String(newValue); this.previousValue = null; this.operator = null; this.waitingForOperand = true; this.updateDisplay(); } performCalculation(operator, firstValue, secondValue) { let result; try { switch (operator) { case '+': result = firstValue + secondValue; break; case '-': result = firstValue - secondValue; break; case '×': result = firstValue * secondValue; break; case '÷': if (secondValue === 0) { this.showError('ゼロで割ることはできません'); return null; } result = firstValue / secondValue; break; case '%': result = (firstValue / 100) * secondValue; break; default: return null; } // 結果の桁数制限(小数点以下10桁まで) if (result % 1 !== 0) { result = parseFloat(result.toFixed(10)); } // オーバーフロー検出 if (!isFinite(result)) { this.showError('計算結果が範囲を超えています'); return null; } return result; } catch (error) { this.showError('計算エラーが発生しました'); return null; } } allClear() { this.currentValue = '0'; this.previousValue = null; this.operator = null; this.waitingForOperand = false; this.updateDisplay(); } clearEntry() { this.currentValue = '0'; this.updateDisplay(); } updateDisplay() { this.display.value = this.currentValue; } showError(message) { this.display.value = 'エラー'; setTimeout(() => { this.allClear(); }, 2000); console.error('Calculator Error:', message); } handleKeyboardInput(e) { e.preventDefault(); const key = e.key; if (key >= '0' && key <= '9') { this.inputNumber(key); } else if (key === '.') { this.inputDecimal(); } else if (key === '+') { this.inputOperator('+'); } else if (key === '-') { this.inputOperator('-'); } else if (key === '*') { this.inputOperator('×'); } else if (key === '/') { this.inputOperator('÷'); } else if (key === '%') { this.inputOperator('%'); } else if (key === 'Enter' || key === '=') { this.calculate(); } else if (key === 'Escape') { this.allClear(); } else if (key === 'Backspace') { this.clearEntry(); } }}
// アプリケーション開始document.addEventListener('DOMContentLoaded', () => { new Calculator();});
コードの詳細解説
重要な部分を詳しく見てみましょう。
// 状態管理this.currentValue = '0';this.previousValue = null;this.operator = null;this.waitingForOperand = false;
電卓の状態を4つの変数で管理しています。
// エラーハンドリングif (secondValue === 0) { this.showError('ゼロで割ることはできません'); return null;}
ゼロ除算エラーなど、計算で起こりうるエラーを適切に処理しています。
// キーボード対応document.addEventListener('keydown', (e) => { this.handleKeyboardInput(e);});
マウスだけでなく、キーボードでも操作できるようにしています。
このアプリで、複雑な状態管理とエラー処理が学べます。
アプリ4: デジタル時計(日時処理編)
身につくスキル
日時処理とCanvas描画を学びましょう。
- Date オブジェクトの操作
- setInterval による定期実行
- 文字列フォーマット
- アナログ時計の描画
- タイムゾーン処理
- アラーム機能
時間を扱うアプリ開発の基本が身につきます。
HTML構造
多機能時計のレイアウトを作成します。
<!DOCTYPE html><html lang="ja"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>デジタル時計</title> <style> body { font-family: 'Courier New', monospace; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); margin: 0; padding: 20px; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; color: white; } .clock-container { text-align: center; background: rgba(255,255,255,0.1); padding: 40px; border-radius: 20px; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0,0,0,0.1); } .digital-clock { font-size: 4rem; font-weight: bold; margin: 20px 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } .date-display { font-size: 1.5rem; margin: 10px 0; opacity: 0.9; } .timezone-selector { margin: 20px 0; padding: 10px; border-radius: 5px; border: none; background: rgba(255,255,255,0.2); color: white; } .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 30px; } .feature-card { background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; backdrop-filter: blur(5px); } .alarm-input { padding: 8px; border-radius: 5px; border: none; background: rgba(255,255,255,0.2); color: white; margin: 5px; } .btn { padding: 10px 20px; border: none; border-radius: 5px; background: #ff6b6b; color: white; cursor: pointer; margin: 5px; transition: background-color 0.3s; } .btn:hover { background: #ff5252; } .stopwatch { font-size: 2rem; margin: 10px 0; } .analog-clock { width: 200px; height: 200px; margin: 20px auto; } </style></head><body> <div class="clock-container"> <h1>多機能デジタル時計</h1> <div class="digital-clock" id="digital-clock">00:00:00</div> <div class="date-display" id="date-display">2024年1月1日 月曜日</div> <select class="timezone-selector" id="timezone-selector"> <option value="Asia/Tokyo">日本時間 (JST)</option> <option value="America/New_York">ニューヨーク (EST)</option> <option value="Europe/London">ロンドン (GMT)</option> <option value="Asia/Shanghai">上海 (CST)</option> </select> <div class="features"> <div class="feature-card"> <h3>アラーム</h3> <input type="time" class="alarm-input" id="alarm-time"> <input type="text" class="alarm-input" id="alarm-message" placeholder="メッセージ"> <button class="btn" id="set-alarm">アラーム設定</button> <div id="alarm-status">アラーム: 未設定</div> </div> <div class="feature-card"> <h3>ストップウォッチ</h3> <div class="stopwatch" id="stopwatch">00:00:00.000</div> <button class="btn" id="start-stop">開始</button> <button class="btn" id="reset-stopwatch">リセット</button> </div> <div class="feature-card"> <h3>アナログ時計</h3> <canvas class="analog-clock" id="analog-clock" width="200" height="200"></canvas> </div> </div> </div> <script src="clock.js"></script></body></html>
見た目も美しい多機能時計です。
JavaScript実装(一部抜粋)
時計の核となる部分を実装します。
class MultiFunctionClock { constructor() { this.currentTimezone = 'Asia/Tokyo'; this.alarmTime = null; this.alarmActive = false; this.stopwatchRunning = false; this.stopwatchStartTime = 0; this.stopwatchElapsed = 0; this.initializeElements(); this.attachEventListeners(); this.startClock(); } initializeElements() { this.digitalClock = document.getElementById('digital-clock'); this.dateDisplay = document.getElementById('date-display'); this.timezoneSelector = document.getElementById('timezone-selector'); this.alarmTimeInput = document.getElementById('alarm-time'); this.setAlarmBtn = document.getElementById('set-alarm'); this.alarmStatus = document.getElementById('alarm-status'); this.stopwatchDisplay = document.getElementById('stopwatch'); this.startStopBtn = document.getElementById('start-stop'); this.analogClock = document.getElementById('analog-clock'); } startClock() { this.updateClock(); setInterval(() => this.updateClock(), 1000); } updateClock() { const now = new Date(); // デジタル時計の表示 const timeString = now.toLocaleTimeString('ja-JP', { timeZone: this.currentTimezone, hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); this.digitalClock.textContent = timeString; // 日付の表示 const dateString = now.toLocaleDateString('ja-JP', { timeZone: this.currentTimezone, year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }); this.dateDisplay.textContent = dateString; // アナログ時計の描画 this.drawAnalogClock(now); // アラームチェック this.checkAlarm(now); } drawAnalogClock(date) { const canvas = this.analogClock; const ctx = canvas.getContext('2d'); const centerX = 100; const centerY = 100; const radius = 90; // キャンバスをクリア ctx.clearRect(0, 0, 200, 200); // 時計の外枠 ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); ctx.strokeStyle = 'white'; ctx.lineWidth = 3; ctx.stroke(); // 時刻の取得 const timeInTimezone = new Date(date.toLocaleString('en-US', { timeZone: this.currentTimezone })); const hours = timeInTimezone.getHours() % 12; const minutes = timeInTimezone.getMinutes(); const seconds = timeInTimezone.getSeconds(); // 時針 const hourAngle = ((hours + minutes / 60) * 30 - 90) * Math.PI / 180; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.lineTo( centerX + Math.cos(hourAngle) * 50, centerY + Math.sin(hourAngle) * 50 ); ctx.strokeStyle = 'white'; ctx.lineWidth = 6; ctx.stroke(); // 分針 const minuteAngle = ((minutes + seconds / 60) * 6 - 90) * Math.PI / 180; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.lineTo( centerX + Math.cos(minuteAngle) * 70, centerY + Math.sin(minuteAngle) * 70 ); ctx.strokeStyle = 'white'; ctx.lineWidth = 4; ctx.stroke(); // 秒針 const secondAngle = (seconds * 6 - 90) * Math.PI / 180; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.lineTo( centerX + Math.cos(secondAngle) * 80, centerY + Math.sin(secondAngle) * 80 ); ctx.strokeStyle = '#ff6b6b'; ctx.lineWidth = 2; ctx.stroke(); // 中心の点 ctx.beginPath(); ctx.arc(centerX, centerY, 5, 0, 2 * Math.PI); ctx.fillStyle = 'white'; ctx.fill(); } setAlarm() { const timeValue = this.alarmTimeInput.value; if (!timeValue) { alert('アラーム時刻を設定してください'); return; } this.alarmTime = timeValue; this.alarmActive = true; this.alarmStatus.textContent = `アラーム: ${timeValue}`; } checkAlarm(now) { if (!this.alarmActive || !this.alarmTime) return; const currentTime = now.toLocaleTimeString('en-GB', { timeZone: this.currentTimezone, hour12: false, hour: '2-digit', minute: '2-digit' }); if (currentTime === this.alarmTime) { alert('🚨 アラームの時間です!'); this.alarmActive = false; } }}
// アプリケーション開始document.addEventListener('DOMContentLoaded', () => { new MultiFunctionClock();});
コードの詳細解説
重要な部分を詳しく見てみましょう。
// 定期実行の設定setInterval(() => this.updateClock(), 1000);
1秒ごとに時計を更新しています。
// タイムゾーン対応の時刻表示const timeString = now.toLocaleTimeString('ja-JP', { timeZone: this.currentTimezone, hour12: false});
toLocaleTimeString
で指定したタイムゾーンの時刻を取得できます。
// Canvas での描画ctx.beginPath();ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);ctx.stroke();
Canvas APIを使ってアナログ時計を描画しています。
このアプリで、時間処理とCanvas描画が学べます。
アプリ5: 天気アプリ(API連携編)
身につくスキル
最後はAPI連携を学びましょう。
- fetch API によるHTTP通信
- JSON データの処理
- 非同期処理 (async/await)
- エラーハンドリング
- ローカルストレージ活用
- レスポンシブ UI設計
実際のWebサービスとの連携方法が身につきます。
JavaScript実装(シミュレーター版)
実際のAPIの代わりにシミュレーターを使った実装です。
class WeatherApp { constructor() { this.favoriteCities = this.loadFavorites(); this.initializeElements(); this.attachEventListeners(); this.renderFavorites(); // 初期表示として東京の天気を取得 this.getWeather('Tokyo'); } initializeElements() { this.cityInput = document.getElementById('city-input'); this.searchBtn = document.getElementById('search-btn'); this.loading = document.getElementById('loading'); this.error = document.getElementById('error'); this.currentWeather = document.getElementById('current-weather'); this.cityName = document.getElementById('city-name'); this.temperature = document.getElementById('temperature'); this.weatherDescription = document.getElementById('weather-description'); } async getWeather(cityName) { try { this.showLoading(true); this.hideError(); // 実際のAPIコールの代わりにシミュレーターを使用 const weatherData = await this.simulateWeatherAPI(cityName); this.displayCurrentWeather(weatherData); this.showLoading(false); } catch (error) { this.showLoading(false); this.showError(`天気情報の取得に失敗しました: ${error.message}`); console.error('Weather API Error:', error); } } async simulateWeatherAPI(cityName) { // 実際のAPI呼び出しのシミュレーション return new Promise((resolve, reject) => { setTimeout(() => { // 一部の都市データをシミュレート const weatherSimulation = { 'tokyo': { name: '東京', country: 'JP', temp: 22, feels_like: 25, humidity: 65, pressure: 1013, wind_speed: 3.2, weather: '晴れ', description: '快晴', icon: '☀️' }, 'new york': { name: 'ニューヨーク', country: 'US', temp: 18, feels_like: 16, humidity: 72, pressure: 1008, wind_speed: 4.1, weather: '曇り', description: '曇り空', icon: '☁️' }, 'london': { name: 'ロンドン', country: 'GB', temp: 15, feels_like: 13, humidity: 78, pressure: 1015, wind_speed: 2.8, weather: '小雨', description: '小雨が降っています', icon: '🌧️' } }; const key = cityName.toLowerCase(); const data = weatherSimulation[key]; if (data) { resolve(data); } else { reject(new Error('都市が見つかりません')); } }, 1000); // API呼び出しの遅延をシミュレート }); } displayCurrentWeather(data) { this.cityName.textContent = `${data.name}, ${data.country}`; this.temperature.textContent = `${Math.round(data.temp)}°C`; this.weatherDescription.textContent = data.description; this.currentWeather.style.display = 'block'; this.currentCityName = data.name; } showLoading(show) { this.loading.style.display = show ? 'block' : 'none'; if (show) { this.currentWeather.style.display = 'none'; } } showError(message) { this.error.textContent = message; this.error.style.display = 'block'; } hideError() { this.error.style.display = 'none'; } addToFavorites() { if (!this.currentCityName) return; if (!this.favoriteCities.includes(this.currentCityName)) { this.favoriteCities.push(this.currentCityName); this.saveFavorites(); this.renderFavorites(); } } saveFavorites() { localStorage.setItem('weatherApp_favorites', JSON.stringify(this.favoriteCities)); } loadFavorites() { const saved = localStorage.getItem('weatherApp_favorites'); return saved ? JSON.parse(saved) : ['Tokyo', 'New York', 'London']; }}
// アプリケーション開始document.addEventListener('DOMContentLoaded', () => { new WeatherApp();});
コードの詳細解説
重要な部分を詳しく見てみましょう。
// 非同期処理の基本async getWeather(cityName) { try { const weatherData = await this.simulateWeatherAPI(cityName); // 処理成功 } catch (error) { // エラー処理 }}
async/await
を使って非同期処理を読みやすく書いています。
// Promise を使ったAPI呼び出しシミュレーションreturn new Promise((resolve, reject) => { setTimeout(() => { if (data) { resolve(data); } else { reject(new Error('都市が見つかりません')); } }, 1000);});
実際のAPI呼び出しの動作をシミュレートしています。
// エラーハンドリングcatch (error) { this.showError(`天気情報の取得に失敗しました: ${error.message}`); console.error('Weather API Error:', error);}
エラーが発生したときの適切な処理を行っています。
このアプリで、API連携の基本が身につきます。
次のステップとスキルアップ
5つのアプリで身についたスキル
今回のアプリ開発で習得したスキルをまとめてみましょう。
基礎スキル:
- 変数と関数の実践的な使い方
- 条件分岐とループの活用
- オブジェクトとクラスの理解
- エラーハンドリングの重要性
DOM操作:
- HTML要素の動的操作
- イベントリスナーの設定
- フォーム処理
- CSS クラスの操作
データ処理:
- ローカルストレージの活用
- JSON データの操作
- 配列とオブジェクトの操作
- データの永続化
非同期処理:
- fetch API によるHTTP通信
- Promise と async/await
- タイマー処理
- API レスポンスの処理
これらは実際のWeb開発でも使われる重要なスキルです。
レベルアップのための課題
既存アプリの機能追加
まずは作ったアプリに新機能を追加してみましょう。
数当てゲーム:
- 難易度選択機能(範囲変更)
- ハイスコア記録
- ヒント機能の改良
- ゲーム統計表示
ToDoリスト:
- カテゴリ機能
- 期限設定
- 優先度表示
- 検索機能
電卓:
- 関数電卓機能
- 計算履歴のエクスポート
- メモリ機能
- テーマ変更
デジタル時計:
- カスタムアラーム音
- タイマー機能
- ポモドーロタイマー
- 世界時計機能
天気アプリ:
- 実際のAPIとの連携
- 7日間の天気予報
- 位置情報の活用
- グラフ表示
これらの機能追加で、さらにスキルアップできます。
次に挑戦すべきプロジェクト
中級者向けのプロジェクトも紹介します。
タスク管理アプリ(1-2週間):
- ドラッグ&ドロップ
- データベース連携
- ユーザー認証
ブログシステム(2-3週間):
- Markdown解析
- ファイル操作
- SEO対策
チャットアプリ(2-4週間):
- WebSocket
- リアルタイム通信
- ユーザー管理
これらに挑戦することで、より高度なスキルが身につきます。
学習継続のコツ
プログラミングの上達には継続が重要です。
継続的な学習のポイント:
- 小さな成功を積み重ねる
- 他の人にコードを見せてフィードバックをもらう
- オンラインコミュニティに参加する
- 実際の問題を解決するアプリを作る
- 学習記録をつけて進歩を可視化する
毎日少しずつでも続けることで、必ず上達していきます。
まとめ
JavaScript初心者が最初に作るべき5つのアプリを段階的に実装しました。
今回身についたスキル:
- 数当てゲーム: 基本文法とゲームロジック
- ToDoリスト: DOM操作とデータ管理
- 電卓: 計算処理とエラーハンドリング
- デジタル時計: 日時処理とCanvas描画
- 天気アプリ: API連携と非同期処理
成功のポイント:
- 段階的学習: 簡単なものから複雑なものへ
- 実践重視: 理論だけでなく実際にコードを書く
- エラーを恐れない: エラーは学習の一部
- 継続的改良: 完成後も機能追加や改良を続ける
JavaScriptの基礎が身についたら、次はReactやVue.jsなどのフレームワークに挑戦してみましょう。 今回作ったアプリがポートフォリオとして活用できるはずです。
プログラミングは継続が最も重要です。 毎日少しずつでも続けることで、必ず上達していきます。
ぜひ今回学んだことを活かして、素晴らしいアプリを作ってみてください!