プログラミング初心者がJavaScriptで最初に作るべき5つのアプリ

JavaScript初心者が学習効果を最大化できる5つのアプリを厳選。具体的なコード例と段階的な実装方法で、初心者でも確実に作れるアプリを詳しく解説します。

Learning Next 運営
66 分で読めます

プログラミング初心者が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
  • リアルタイム通信
  • ユーザー管理

これらに挑戦することで、より高度なスキルが身につきます。

学習継続のコツ

プログラミングの上達には継続が重要です。

継続的な学習のポイント

  1. 小さな成功を積み重ねる
  2. 他の人にコードを見せてフィードバックをもらう
  3. オンラインコミュニティに参加する
  4. 実際の問題を解決するアプリを作る
  5. 学習記録をつけて進歩を可視化する

毎日少しずつでも続けることで、必ず上達していきます。

まとめ

JavaScript初心者が最初に作るべき5つのアプリを段階的に実装しました。

今回身についたスキル

  1. 数当てゲーム: 基本文法とゲームロジック
  2. ToDoリスト: DOM操作とデータ管理
  3. 電卓: 計算処理とエラーハンドリング
  4. デジタル時計: 日時処理とCanvas描画
  5. 天気アプリ: API連携と非同期処理

成功のポイント

  • 段階的学習: 簡単なものから複雑なものへ
  • 実践重視: 理論だけでなく実際にコードを書く
  • エラーを恐れない: エラーは学習の一部
  • 継続的改良: 完成後も機能追加や改良を続ける

JavaScriptの基礎が身についたら、次はReactやVue.jsなどのフレームワークに挑戦してみましょう。 今回作ったアプリがポートフォリオとして活用できるはずです。

プログラミングは継続が最も重要です。 毎日少しずつでも続けることで、必ず上達していきます。

ぜひ今回学んだことを活かして、素晴らしいアプリを作ってみてください!

関連記事