「Webサイトにちょっとした便利な機能を追加したいけど、プログラミングってなんだか難しそう…」
「自分で何かツールを作ってみたいけど、何から手をつけていいか分からない」
そんなあなたにピッタリなのが、この記事で紹介する年齢計算ツールの作り方です。
使うのは、HTML・CSS・JavaScriptというWeb開発の基本3言語だけ。
難しいフレームワークは一切不要。
コピペOKのサンプルコードと初心者向けの丁寧な解説で、誰でも「動くWebツール」が作れるようになります!
完成品は、干支・星座・和暦にまで対応した本格仕様。
この記事を読み終える頃には、あなたも立派なWebツール職人になっているはずです。
「年齢計算ツール」でできること(機能紹介)
今回作る年齢計算ツールは、次のような便利な機能を備えています。
- 生年月日を基に、様々な情報が瞬時に表示されます。
- 満年齢と数え年の表示
- 干支と星座の表示
- 生年月日からの正確な経過日数
- 次の誕生日までの残り日数
- 人生の節目タイムライン
誕生から入学、卒業、成人、長寿のお祝いまで、主要なイベントを時系列で自動生成。
- 以下の便利機能も搭載
- 和暦対応: 明治、大正、昭和、平成、令和の和暦での生年月日入力・表示に対応
- 結果のコピー機能: 計算結果とタイムラインの内容をまとめてコピーできる。
【実際の年齢計算ツールの動作を試してみたい方はこちらからどうぞ】▼
年齢計算ツールってどんな仕組み?(基本概念の解説)
まずはざっくり仕組みを知っておきましょう。
- HTMLの役割:
- ツールの「見た目」や「入力フォーム」を作るための言語。
<html><body><input><button>などのタグで構成される。- 専門用語:HTML(HyperText Markup Language - Webページの構造を定義するマークアップ言語)
- CSSの役割:
- ツールの「デザイン」や「レイアウト」を整えるための言語。
- 色や配置、文字の大きさなどを指定する。
- 専門用語:CSS(Cascading Style Sheets - Webページのスタイルを指定するスタイルシート言語)
- JavaScriptの役割:
- ツールの「計算」や「動き」を制御するプログラミング言語。
- ユーザーの入力(生年月日)を受け取り、計算し、結果を表示する。
- 専門用語:JavaScript(Webページに動きをつけるためのプログラミング言語)
この3つを組み合わせることで、年齢や干支、星座を自動計算するインタラクティブなWebツールが完成します。
開発に必要な準備とファイル構成
特別なソフトやサーバーは不要です。すべてローカル環境(自分のPC)で完結します。
必要なものはたったこれだけ!
- テキストエディタ(例:メモ帳、Visual Studio Code)
- Webブラウザ(例:Google Chrome、Microsoft Edge、Firefoxなど)
VS Code (Visual Studio Code) とは
Microsoftが開発している、プログラミングコードを書くための高機能な無料テキストエディタです。
たくさんの便利な機能があり、世界中の開発者に愛用されています。
実際にコードを書いてみよう!(実践編)
ここからは、実際に年齢計算ツールのコードを書いていきます。全てのコードを一度に理解しようとしなくても大丈夫。まずはコピペで動かしてみて、その後に「なぜこうなるのか」を少しずつ確認していくのが習得の近道ですよ。
まず、パソコンのどこかに新しくフォルダを作成し、その中に index.html という名前のファイルを作成してください。このファイルにこれから紹介するコードを貼り付けていきます。
ステップ1: HTMLで骨組みを作ろう
index.html ファイルに、以下のHTMLコードをコピー&ペーストしてください。
|
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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>年齢・経過日数計算ツール</title> <style> /* CSSコードはここに記述 */ </style> </head> <body> <div class="container"> <h1>年齢・経過日数計算ツール</h1> <div class="form-group"> <label>生年月日入力形式:</label> <div class="radio-group"> <label><input type="radio" name="dateType" value="seireki" checked onchange="toggleDateInput()"> 西暦</label> <label><input type="radio" name="dateType" value="wareki" onchange="toggleDateInput()"> 和暦</label> </div> </div> <div class="form-group" id="seirekiInput"> <label for="birthDateSeireki">生年月日 (西暦):</label> <div class="input-group"> <input type="number" id="birthYearSeireki" class="year-input" placeholder="年 (例: 1990)" min="1900" max="2100"> <input type="number" id="birthMonthSeireki" class="month-day-input" placeholder="月 (1-12)" min="1" max="12"> <input type="number" id="birthDaySeireki" class="month-day-input" placeholder="日 (1-31)" min="1" max="31"> </div> </div> <div class="form-group hidden" id="warekiInput"> <label for="birthDateWareki">生年月日 (和暦):</label> <div class="input-group"> <select id="warekiEra"> <option value="">元号を選択</option> <option value="meiji">明治</option> <option value="taisho">大正</option> <option value="showa">昭和</option> <option value="heisei">平成</option> <option value="reiwa">令和</option> </select> <input type="number" id="birthYearWareki" class="year-input" placeholder="年 (例: 1)" min="1" max="100"> <input type="number" id="birthMonthWareki" class="month-day-input" placeholder="月 (1-12)" min="1" max="12"> <input type="number" id="birthDayWareki" class="month-day-input" placeholder="日 (1-31)" min="1" max="31"> </div> </div> <div class="form-group"> <label for="targetDate">基準日:</label> <div class="input-group"> <input type="number" id="targetYear" class="year-input" placeholder="年" min="1900" max="2100"> <input type="number" id="targetMonth" class="month-day-input" placeholder="月" min="1" max="12"> <input type="number" id="targetDay" class="month-day-input" placeholder="日" min="1" max="31"> </div> </div> <button onclick="calculateAgeAndDays()">計算</button> <div id="errorMessage" class="error"></div> <div id="result" class="result" style="display: none;"> <p><strong>満年齢:</strong> <span id="fullAgeResult"></span> 歳</p> <p><strong>数え年:</b> <span id="countingAgeResult"></span> 歳</p> <p><strong>干支:</strong> <span id="etoResult"></span></p> <p><strong>星座:</strong> <span id="zodiacResult"></span></p> <p><strong>生年月日からの経過日数:</strong> <span id="daysResult"></span> 日</p> <p><strong>次の誕生日まで:</strong> <span id="nextBirthdayResult"></span></p> <div class="copy-button-container"> <button class="copy-button" onclick="copyResultToClipboard()">結果をコピー</button> </div> <div id="timelineContainer" class="timeline" style="display: none;"> <h2>人生の節目タイムライン</h2> <div id="timelineEvents"> </div> </div> </div> </div> <script> // JavaScriptコードはここに記述 </script> </body> </html> |
たくさんのコードに見えるかもしれませんが、これはWebページの大枠(見出し、入力欄、ボタン、結果を表示する場所)を作っているだけです。まだ動かなくても大丈夫ですよ!
ステップ2: CSSで見た目を整えよう
次に、ツールの見た目を整えるためのCSSコードを、先ほどのHTMLコードの <style> タグの中に貼り付けてください。
|
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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
/* CSSコードはここに記述 */ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 20px; background-color: #f4f7f6; color: #333; line-height: 1.6; } .container { max-width: 800px; margin: 30px auto; padding: 30px; background-color: #fff; border-radius: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); } h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; font-size: 2em; } .form-group { margin-bottom: 20px; } label { display: block; margin-bottom: 8px; font-weight: bold; color: #555; } .input-group { display: flex; gap: 10px; align-items: center; } .input-group input[type="number"], .input-group select { padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 1em; box-sizing: border-box; } .input-group input[type="number"].year-input { flex: 1.5; max-width: 120px; } .input-group input[type="number"].month-day-input { flex: 1; max-width: 80px; } .input-group select#warekiEra { flex: 1.2; max-width: 100px; } .radio-group { display: flex; gap: 20px; margin-bottom: 15px; } .radio-group label { display: flex; align-items: center; font-weight: normal; } .radio-group input[type="radio"] { margin-right: 5px; } button { display: block; width: 100%; padding: 12px 20px; background-color: #3498db; color: white; border: none; border-radius: 5px; font-size: 1.1em; cursor: pointer; transition: background-color 0.3s ease; margin-top: 20px; } button:hover { background-color: #2980b9; } .result { margin-top: 30px; padding: 20px; background-color: #e8f6fd; border: 1px solid #cceeff; border-radius: 8px; font-size: 1.1em; color: #2c3e50; } .result p { margin: 8px 0; } .result strong { color: #2c3e50; } .error { color: #e74c3c; margin-top: 10px; font-weight: bold; } .hidden { display: none; } /* Timeline styles */ .timeline { margin-top: 40px; border-left: 3px solid #3498db; padding-left: 20px; position: relative; max-height: 400px; /* タイムラインの高さ固定 */ overflow-y: auto; /* 縦方向のスクロールバーを表示 */ padding-right: 10px; } .timeline h2 { color: #2c3e50; margin-bottom: 20px; text-align: left; } .timeline-item { margin-bottom: 25px; position: relative; padding-left: 20px; } .timeline-item::before { content: ''; position: absolute; left: -32px; top: 5px; width: 12px; height: 12px; background-color: #3498db; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 2px #3498db; } .timeline-item h3 { margin: 0 0 5px 0; color: #3498db; font-size: 1.2em; } .timeline-item p { margin: 0; font-size: 0.95em; color: #666; } .copy-button-container { text-align: right; margin-top: 15px; } .copy-button { background-color: #28a745; color: white; padding: 8px 15px; border: none; border-radius: 5px; cursor: pointer; font-size: 0.9em; transition: background-color 0.3s ease; } .copy-button:hover { background-color: #218838; } |
これで見た目がガラッと変わるはず!Webページに色がついて、それぞれの要素が綺麗に配置されますよ。
ステップ3: JavaScriptで計算機能を実装しよう
最後に、ツールの「頭脳」となるJavaScriptコードを、HTMLの最後の </body> タグの直前にある <script> タグの中に貼り付けてください。
|
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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
document.addEventListener('DOMContentLoaded', function() { // 今日の日付をデフォルトで基準日にセット const today = new Date(); document.getElementById('targetYear').value = today.getFullYear(); document.getElementById('targetMonth').value = today.getMonth() + 1; document.getElementById('targetDay').value = today.getDate(); }); // 和暦と西暦の入力フォーム切り替え function toggleDateInput() { const seirekiInput = document.getElementById('seirekiInput'); const warekiInput = document.getElementById('warekiInput'); const dateType = document.querySelector('input[name="dateType"]:checked').value; if (dateType === 'seireki') { seirekiInput.classList.remove('hidden'); warekiInput.classList.add('hidden'); } else { // wareki seirekiInput.classList.add('hidden'); warekiInput.classList.remove('hidden'); } // 入力値をクリアして誤入力を防ぐ clearInputs(); } function clearInputs() { document.getElementById('birthYearSeireki').value = ''; document.getElementById('birthMonthSeireki').value = ''; document.getElementById('birthDaySeireki').value = ''; // 和暦の入力フィールドもクリア document.getElementById('warekiEra').value = ''; document.getElementById('birthYearWareki').value = ''; document.getElementById('birthMonthWareki').value = ''; document.getElementById('birthDayWareki').value = ''; } // 和暦から西暦への変換データ const warekiEras = { meiji: { name: "明治", startYear: 1868, startDate: new Date(1868, 9, 23), endYear: 1912, endDate: new Date(1912, 6, 29) }, taisho: { name: "大正", startYear: 1912, startDate: new Date(1912, 6, 30), endYear: 1926, endDate: new Date(1926, 11, 24) }, showa: { name: "昭和", startYear: 1926, startDate: new Date(1926, 11, 25), endYear: 1989, endDate: new Date(1989, 0, 7) }, heisei: { name: "平成", startYear: 1989, startDate: new Date(1989, 0, 8), endYear: 2019, endDate: new Date(2019, 3, 30) }, reiwa: { name: "令和", startYear: 2019, startDate: new Date(2019, 4, 1), endYear: null, endDate: null } // 令和の終わりは未定 }; // 西暦から和暦への変換 (タイムライン表示用) function convertSeirekiToWareki(seirekiDate) { const year = seirekiDate.getFullYear(); const month = seirekiDate.getMonth(); // 0-11 const day = seirekiDate.getDate(); for (const eraKey in warekiEras) { const eraInfo = warekiEras[eraKey]; const eraStart = eraInfo.startDate; const eraEnd = eraInfo.endDate; const targetDate = new Date(year, month, day); if (targetDate >= eraStart && (!eraEnd || targetDate <= eraEnd)) { let warekiYear = year - eraInfo.startYear + 1; return `${eraInfo.name}${warekiYear}年`; } } return ""; // 該当する和暦がない場合 } function convertWarekiToSeireki(era, year, month, day) { const eraInfo = warekiEras[era]; if (!eraInfo) return null; let seirekiYear; if (year === 1) { // 元年の場合 seirekiYear = eraInfo.startYear; } else { seirekiYear = eraInfo.startYear + year - 1; } const convertedDate = new Date(seirekiYear, month - 1, day); if (convertedDate.getFullYear() !== seirekiYear || convertedDate.getMonth() !== (month - 1) || convertedDate.getDate() !== day) { return null; // 存在しない日付と判断 } if (convertedDate < eraInfo.startDate) { return null; } if (eraInfo.endDate && convertedDate > eraInfo.endDate) { return null; } return { year: seirekiYear, month: month, day: day }; } // 星座判定ロジック function getZodiacSign(month, day) { const m = month; if ((m === 1 && day >= 20) || (m === 2 && day <= 18)) return "水瓶座(みずがめざ)"; if ((m === 2 && day >= 19) || (m === 3 && day <= 20)) return "魚座(うおざ)"; if ((m === 3 && day >= 21) || (m === 4 && day <= 19)) return "牡羊座(おひつじざ)"; if ((m === 4 && day >= 20) || (m === 5 && day <= 20)) return "牡牛座(おうしざ)"; if ((m === 5 && day >= 21) || (m === 6 && day <= 21)) return "双子座(ふたござ)"; if ((m === 6 && day >= 22) || (m === 7 && day <= 22)) return "蟹座(かにざ)"; if ((m === 7 && day >= 23) || (m === 8 && day <= 22)) return "獅子座(ししざ)"; if ((m === 8 && day >= 23) || (m === 9 && day <= 22)) return "乙女座(おとめざ)"; if ((m === 9 && day >= 23) || (m === 10 && day <= 23)) return "天秤座(てんびんざ)"; if ((m === 10 && day >= 24) || (m === 11 && day <= 22)) return "蠍座(さそりざ)"; if ((m === 11 && day >= 23) || (m === 12 && day <= 21)) return "射手座(いてざ)"; if ((m === 12 && day >= 22) || (m === 1 && day <= 19)) return "山羊座(やぎざ)"; return "不明"; } // 曜日を取得するヘルパー関数 function getDayOfWeek(date) { const days = ['日', '月', '火', '水', '木', '金', '土']; return days[date.getDay()]; } function calculateAgeAndDays() { let birthYear, birthMonth, birthDay; const dateType = document.querySelector('input[name="dateType"]:checked').value; const errorMessageDiv = document.getElementById('errorMessage'); const resultDiv = document.getElementById('result'); const timelineContainer = document.getElementById('timelineContainer'); errorMessageDiv.textContent = ''; resultDiv.style.display = 'none'; timelineContainer.style.display = 'none'; if (dateType === 'seireki') { birthYear = parseInt(document.getElementById('birthYearSeireki').value); birthMonth = parseInt(document.getElementById('birthMonthSeireki').value); birthDay = parseInt(document.getElementById('birthDaySeireki').value); } else { // wareki const warekiEra = document.getElementById('warekiEra').value; const warekiYear = parseInt(document.getElementById('birthYearWareki').value); const warekiMonth = parseInt(document.getElementById('birthMonthWareki').value); const warekiDay = parseInt(document.getElementById('birthDayWareki').value); if (!warekiEra || isNaN(warekiYear) || isNaN(warekiMonth) || isNaN(warekiDay)) { errorMessageDiv.textContent = '和暦のすべての項目に有効な数字を入力し、元号を選択してください。'; return; } const converted = convertWarekiToSeireki(warekiEra, warekiYear, warekiMonth, warekiDay); if (!converted) { errorMessageDiv.textContent = '入力された和暦は、その元号の期間外か、または無効な日付です。詳細を確認してください。'; return; } birthYear = converted.year; birthMonth = converted.month; birthDay = converted.day; } const targetYear = parseInt(document.getElementById('targetYear').value); const targetMonth = parseInt(document.getElementById('targetMonth').value); const targetDay = parseInt(document.getElementById('targetDay').value); if (isNaN(birthYear) || isNaN(birthMonth) || isNaN(birthDay) || isNaN(targetYear) || isNaN(targetMonth) || isNaN(targetDay)) { errorMessageDiv.textContent = 'すべての項目に有効な数字を入力してください。'; return; } const birthDate = new Date(birthYear, birthMonth - 1, birthDay); const targetDate = new Date(targetYear, targetMonth - 1, targetDay); if (birthDate.getFullYear() !== birthYear || birthDate.getMonth() !== (birthMonth - 1) || birthDate.getDate() !== birthDay) { errorMessageDiv.textContent = '生年月日が有効な日付ではありません。例:2月30日など。'; return; } if (targetDate.getFullYear() !== targetYear || targetDate.getMonth() !== (targetMonth - 1) || targetDate.getDate() !== targetDay) { errorMessageDiv.textContent = '基準日が有効な日付ではありません。例:2月30日など。'; return; } if (targetDate < birthDate) { errorMessageDiv.textContent = '基準日は生年月日より後の日付にしてください。'; return; } let fullAge = targetDate.getFullYear() - birthDate.getFullYear(); const monthDiff = targetDate.getMonth() - birthDate.getMonth(); const dayDiff = targetDate.getDate() - birthDate.getDate(); if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) { fullAge--; } const countingAge = fullAge + 1; const etoList = [ "子(ね)", "丑(うし)", "寅(とら)", "卯(う)", "辰(たつ)", "巳(み)", "午(うま)", "未(ひつじ)", "申(さる)", "酉(とり)", "戌(いぬ)", "亥(い)" ]; const eto = etoList[(birthYear - 1900) % 12]; const zodiac = getZodiacSign(birthMonth, birthDay); const utcBirthDate = Date.UTC(birthYear, birthMonth - 1, birthDay); const utcTargetDate = Date.UTC(targetYear, targetMonth - 1, targetDay); const diffTime = utcTargetDate - utcBirthDate; const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); let nextBirthday = new Date(targetYear, birthMonth - 1, birthDay); if (nextBirthday < targetDate) { nextBirthday.setFullYear(targetYear + 1); } if (birthMonth === 2 && birthDay === 29 && !isLeapYear(nextBirthday.getFullYear())) { nextBirthday = new Date(nextBirthday.getFullYear(), 1, 28); } const timeToNextBirthday = nextBirthday.getTime() - targetDate.getTime(); const daysToNextBirthday = Math.ceil(timeToNextBirthday / (1000 * 60 * 60 * 24)); document.getElementById('fullAgeResult').textContent = fullAge; document.getElementById('countingAgeResult').textContent = countingAge; document.getElementById('etoResult').textContent = eto; document.getElementById('zodiacResult').textContent = zodiac; document.getElementById('daysResult').textContent = diffDays; document.getElementById('nextBirthdayResult').textContent = `${nextBirthday.getFullYear()}年${nextBirthday.getMonth() + 1}月${nextBirthday.getDate()}日 (${getDayOfWeek(nextBirthday)}曜日) (${fullAge + 1}歳) まであと ${daysToNextBirthday} 日`; resultDiv.style.display = 'block'; generateTimeline(birthDate, targetDate, fullAge); } async function copyResultToClipboard() { let resultText = ''; resultText += `満年齢: ${document.getElementById('fullAgeResult').textContent} 歳\n`; resultText += `数え年: ${document.getElementById('countingAgeResult').textContent} 歳\n`; resultText += `干支: ${document.getElementById('etoResult').textContent}\n`; resultText += `星座: ${document.getElementById('zodiacResult').textContent}\n`; resultText += `生年月日からの経過日数: ${document.getElementById('daysResult').textContent} 日\n`; resultText += `次の誕生日まで: ${document.getElementById('nextBirthdayResult').textContent}\n\n`; const timelineEventsDiv = document.getElementById('timelineEvents'); if (timelineEventsDiv.children.length > 0) { resultText += "人生の節目タイムライン:\n"; Array.from(timelineEventsDiv.children).forEach(item => { const date = item.querySelector('h3').textContent; const details = item.querySelector('p').textContent; resultText += ` ${date} - ${details}\n`; }); } try { await navigator.clipboard.writeText(resultText); alert('計算結果がクリップボードにコピーされました!'); } catch (err) { console.error('テキストのコピーに失敗しました', err); alert('結果のコピーに失敗しました。お使いのブラウザではサポートされていない可能性があります。'); } } function generateTimeline(birthDate, targetDate, currentAge) { const timelineEventsDiv = document.getElementById('timelineEvents'); timelineEventsDiv.innerHTML = ''; const timelineContainer = document.getElementById('timelineContainer'); timelineContainer.style.display = 'block'; let events = []; events.push({ date: birthDate, label: "誕生", detail: `満0歳` }); for (const eraKey in warekiEras) { const eraInfo = warekiEras[eraKey]; if (eraInfo.startDate >= birthDate && eraInfo.startDate <= targetDate) { if (eraInfo.startDate.toDateString() === birthDate.toDateString()) { continue; } events.push({ date: eraInfo.startDate, label: `${eraInfo.name}改元`, detail: `${eraInfo.name}元年` }); } } const birthMonth = birthDate.getMonth() + 1; const birthDay = birthDate.getDate(); const getEnrollmentYear = (baseYear, ageAtEnrollment) => { if (birthMonth > 4 || (birthMonth === 4 && birthDay > 1)) { return baseYear + ageAtEnrollment + 1; } return baseYear + ageAtEnrollment; }; const elementarySchoolEnrollmentYear = getEnrollmentYear(birthDate.getFullYear(), 6); const elementarySchoolEnrollmentDate = new Date(elementarySchoolEnrollmentYear, 3, 1); if (elementarySchoolEnrollmentDate <= targetDate) { events.push({ date: elementarySchoolEnrollmentDate, label: "小学校入学", detail: `` }); } const juniorHighSchoolEnrollmentYear = elementarySchoolEnrollmentYear + 6; const juniorHighSchoolEnrollmentDate = new Date(juniorHighSchoolEnrollmentYear, 3, 1); if (juniorHighSchoolEnrollmentDate <= targetDate) { events.push({ date: juniorHighSchoolEnrollmentDate, label: "中学校入学", detail: `` }); } const highSchoolEnrollmentYear = juniorHighSchoolEnrollmentYear + 3; const highSchoolEnrollmentDate = new Date(highSchoolEnrollmentYear, 3, 1); if (highSchoolEnrollmentDate <= targetDate) { events.push({ date: highSchoolEnrollmentDate, label: "高校入学", detail: `` }); } const highSchoolGraduationYear = highSchoolEnrollmentYear + 3; const highSchoolGraduationDate = new Date(highSchoolGraduationYear, 2, 31); if (highSchoolGraduationDate <= targetDate) { events.push({ date: highSchoolGraduationDate, label: "高校卒業", detail: `` }); } const comingOfAge20 = 20; const comingOfAgeDate20 = new Date(birthDate.getFullYear() + comingOfAge20, birthDate.getMonth(), birthDate.getDate()); if (comingOfAgeDate20 <= targetDate) { events.push({ date: comingOfAgeDate20, label: `成人式 (満20歳)`, detail: `` }); } const comingOfAge18 = 18; const comingOfAgeDate18 = new Date(birthDate.getFullYear() + comingOfAge18, birthDate.getMonth(), birthDate.getDate()); if (comingOfAgeDate18 <= targetDate && (birthDate.getFullYear() + comingOfAge18 >= 2022)) { events.push({ date: comingOfAgeDate18, label: `成人 (満18歳)`, detail: `` }); } for (let age = 1; age <= currentAge + 5; age++) { let eventLabel = `${age}歳`; let detail = `満${age}歳`; let addEvent = false; if (age === 1) { addEvent = true; } const longevityNames = { 60: "還暦", 70: "古希", 77: "喜寿", 80: "傘寿", 88: "米寿", 90: "卒寿", 99: "白寿", 100: "百寿" }; if (longevityNames[age]) { eventLabel = longevityNames[age]; detail = `満${age}歳 (${eventLabel})`; addEvent = true; } const specificAgesForExclusion = new Set([ (elementarySchoolEnrollmentYear - birthDate.getFullYear()), (juniorHighSchoolEnrollmentYear - birthDate.getFullYear()), (highSchoolEnrollmentYear - birthDate.getFullYear()), (highSchoolGraduationYear - birthDate.getFullYear()), comingOfAge18, comingOfAge20 ]); if (age % 5 === 0 && !longevityNames[age] && !specificAgesForExclusion.has(age)) { addEvent = true; } if (age === currentAge + 1) { eventLabel = `次の誕生日`; detail = `満${age}歳を迎えます`; addEvent = true; } if (addEvent) { let eventDate = new Date(birthDate.getFullYear() + age, birthDate.getMonth(), birthDate.getDate()); if (birthDate.getMonth() === 1 && birthDate.getDate() === 29 && !isLeapYear(eventDate.getFullYear())) { eventDate = new Date(eventDate.getFullYear(), 1, 28); } if (eventDate <= targetDate || age === currentAge + 1) { const isDuplicateSchoolOrGraduationEvent = events.some(e => e.date.toDateString() === eventDate.toDateString() && (e.label.includes("小学校") || e.label.includes("中学校") || e.label.includes("高校")) ); const isDuplicateSpecificAgeEvent = events.some(e => e.date.toDateString() === eventDate.toDateString() && (e.label.includes("成人") || longevityNames[age] === e.label) ); const isDuplicateEraChange = events.some(e => e.date.toDateString() === eventDate.toDateString() && e.label.includes("改元") ); if (!isDuplicateSchoolOrGraduationEvent && !isDuplicateSpecificAgeEvent && !isDuplicateEraChange ) { events.push({ date: eventDate, label: eventLabel, detail: `${detail}` }); } } } } events.sort((a, b) => a.date - b.date); const uniqueEvents = []; const seen = new Set(); events.forEach(event => { const key = `${event.date.toISOString().split('T')[0]}-${event.label}`; if (!seen.has(key)) { uniqueEvents.push(event); seen.add(key); } }); uniqueEvents.forEach(event => { const itemDiv = document.createElement('div'); itemDiv.classList.add('timeline-item'); const seirekiString = `${event.date.getFullYear()}年${event.date.getMonth() + 1}月${event.date.getDate()}日 (${getDayOfWeek(event.date)}曜日)`; const warekiString = convertSeirekiToWareki(event.date); let dateDisplay = seirekiString; if (warekiString) { dateDisplay += ` [${warekiString}]`; } itemDiv.innerHTML = `<h3>${dateDisplay}</h3><p><strong>${event.label}</strong>: ${event.detail}</p>`; timelineEventsDiv.appendChild(itemDiv); }); } function isLeapYear(year) { return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0); } |
これがこのツールの心臓部!ちょっと長いですが、日付の計算、干支や星座の判定、そしてタイムラインの生成まで、全部このJavaScriptがやってくれています。
各JavaScriptロジックの簡単な解説
|
1 |
document.addEventListener('DOMContentLoaded', ...) |
- ページが読み込まれた瞬間、基準日を自動で「今日の日付」にセットします。
|
1 |
toggleDateInput() |
- 「西暦/和暦」のラジオボタンで入力フォームの表示を切り替えます。
|
1 |
const warekiEras = {} |
- 明治〜令和の元号データを保持しています。
- 和暦→西暦、または西暦→和暦の変換処理で使用されます。
|
1 |
convertSeirekiToWareki() / convertWarekiToSeireki() |
- 和暦と西暦を相互変換する関数です。
- ユーザーがどちらで入力しても正しく日付計算できます。
|
1 |
getZodiacSign() |
- 月と日から星座を判定します。
- 結果表示エリアの「星座」欄で使用されます。
|
1 |
getDayOfWeek() |
- 日付から曜日(例:月曜日)を取得します。
- 次の誕生日やタイムライン表示に使用します。
|
1 |
calculateAgeAndDays() |
- このツールのメイン処理を担当する関数です。
- 年齢・干支・星座・経過日数などをまとめて計算し、結果表示に反映します。
|
1 |
copyResultToClipboard() |
- 表示された計算結果+タイムラインをワンクリックでコピーできます。
- メモやブログへの転記がスムーズになります。
|
1 |
generateTimeline() |
- 誕生から節目までの人生タイムラインを自動生成します。
- 小学校入学・成人・還暦などのイベントも自動で並びます。
|
1 |
isLeapYear() |
- 年がうるう年かどうかを判定します。
- 2月29日生まれの処理や、誕生日計算の正確さを担保します。
補足:全部理解しなくても大丈夫!
これらの関数は、すでにセット済みのサンプルコードに含まれているので、そのまま使うだけでOK。
少しずつ読み解いていくことで、自然とJavaScriptの力が身についていきます。
完成したツールを動かしてみよう!
すべてのコードを index.html に貼り付けたら、ファイルを保存してください。
- 保存した
index.htmlファイルを、Google ChromeなどのWebブラウザにドラッグ&ドロップしてください。 - ファイルエクスプローラー(Windows)やFinder(Mac)で
index.htmlをダブルクリックして開いてもOKです。
どうでしょうか?入力フォームが表示され、計算ボタンも見えるはずです。
適当な生年月日と基準日を入力して、「計算」ボタンを押してみてください。
動作確認のポイント
- 年齢、干支、星座は正しく表示されますか?
- 和暦に切り替えて入力しても、正しく計算されますか?
- タイムラインは表示され、イベントは時系列順に並んでいますか?
- 「結果をコピー」ボタンは機能しますか?
もしうまく動かない場合は、HTMLやCSS、JavaScriptのコードをもう一度見直してみてください。どこか貼り付け間違いがあるかもしれません。
さらにカスタマイズしてみよう!(応用編)
これで基本的な年齢計算ツールは完成ですが、ここからがプログラミングの醍醐味!自分だけのオリジナルツールにカスタマイズしてみましょう。
- デザインの変更
CSSコードを編集して、色やフォント、レイアウトを自分好みに変えてみてください。
例えば、background-colorを好きな色に変えるだけでも印象は大きく変わります。 - 機能の追加
- 特定の記念日(結婚記念日、開業日など)を追記できるようにする。
- 入力フォームに初期値を設定し、ユーザーが手軽に試せるようにする。
- エラーメッセージをより具体的に表示するなど、ユーザー体験を向上させる。
おすすめの学習リソース(もっと作れるようになりたいあなたへ)
年齢計算ツールを作って「もっと作りたい!」「ちゃんと学んでみたい!」と思ったなら、次は体系的な学習リソースに進むのが最短ルートです。
書籍でじっくり学びたい人におすすめ
『確かな力が身につくJavaScript超入門』
JavaScript完全初心者向けの定番書!
・基本文法から簡単なWebアプリ開発までカバー
・図が多く、スラスラ読める構成
・「とりあえず動かしてみたい」から「仕組みも理解したい」まで対応
『スラスラわかるHTML&CSSのきほん』
見た目の仕組みを楽しく学べる!
・HTMLとCSSの基本を、実際にWebページを作りながら習得
・色、配置、ボタンなどの「見た目のルール」がわかるようになる
・図解と実践が中心なので、初学者でも置いていかれません
Webツールを“自分好みのデザイン”にしたい人に超おすすめ!
オンラインで手を動かしながら学びたい人へ
Udemy(ユーデミー)
プロ講師による動画講座が豊富!
・初心者向け〜実務レベルまで幅広く対応
・動画形式で「見て → 真似して → 自分で書く」流れが作れる
・頻繁にセール開催、1,000円台で買えることも!
まとめ
この記事では、HTML・CSS・JavaScriptの3つだけを使って、
干支・星座・和暦・人生のタイムラインにも対応した年齢計算ツールの作り方を解説しました。
3つの言語を役割ごとに組み合わせれば、一見複雑そうなツールも自分で作れるということが実感できたはずです。
プログラミングは、部品(コード)の組み合わせで動くものを生み出す技術。
どんなに高度に見える仕組みも、一歩一歩理解していけば、必ず自分の手で作れるようになります。
「ちゃんと動いた!」という感動は、まさにプログラミングの醍醐味。
最初はコピペでも大丈夫。手を動かした経験が、確かな力になります。
ぜひ今回のツールを足がかりに、次のツール開発や本格的な学習にもチャレンジしてみてください。


