0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【JavaScript】カレンダーを幾つかGitHubPagesで公開してみた

Last updated at Posted at 2025-04-07

カレンダーアプリ案を幾つか

春なのでカレンダーを作ってみました。
取り敢えず新人への課題や、新しい言語に触れた時のお題になるような気がしています。

  • HTML / CSS / JavaScript(vanillaJS)のフロントエンドのみの構成
  • 曜日の指定方法は2通り。7ごとに改行、grid
    #4/8 8:14追記 コメントで、配列の利用案をいただきました。是非確認してみてください。
  • 整列方法は3通り。テーブル、等幅フォント、grid
  • 自前で閏年計算するパターンのあります
  • スタイルを徐々に設定してみるパートもあります
  • 100年カレンダーも作成

デモ

ソース


1.7ごとに改行し、htmlのtableを利用する案

<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>カレンダー</title></head>
<body>
  <h1 id="today"></h1>
  <table border="1">
    <thead>
      <tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr>
    </thead>
    <tbody id="calbody"></tbody>
  </table>
  <script>
    const t = new Date(), y = t.getFullYear(), m = t.getMonth(), d = t.getDate();
    document.getElementById("today").textContent = `${y}/${m+1}/${d}`;
    const first = new Date(y, m, 1).getDay(), last = new Date(y, m+1, 0).getDate();
    const body = document.getElementById("calbody");
    let row = document.createElement("tr");
    for(let i=0;i<first;i++) row.appendChild(document.createElement("td"));
    for(let day=1; day<=last; day++){
      const td = document.createElement("td");
      td.textContent = day;
      row.appendChild(td);
      if((first + day) % 7 === 0 || day === last){
        body.appendChild(row);
        row = document.createElement("tr");
      }
    }
  </script>
</body>
</html>

  • new Date(年, 月, 1) で1日の曜日を取得
  • getDay() で曜日番号(0:日〜6:土)を取得
  • new Date(年, 月, 0) を使うと「前の月の末日」が取得できます(便利!)
  • 月初の空白は <td></td>で埋める
  • 日付を順番に追加
  • 「(first + day) % 7 === 0」で、剰余を利用して7列ごと改行、または月末で改行(新しい<tr>
  • 何となく懐かしい見栄えになります

2.7ごとに改行し、等幅フォントを利用する案

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>カレンダー</title>
  <style>
    #calendar {
      font-family: monospace;
      white-space: pre;
    }
  </style>
</head>
<body>
  <h1 id="today"></h1>
  <div id="calendar"></div>
  <script>
    const t = new Date(), y = t.getFullYear(), m = t.getMonth(), d = t.getDate();
    document.getElementById("today").textContent = `${y}/${m+1}/${d}`;
    const cal = document.getElementById("calendar");
    const w = ["","","","","","",""];
    cal.innerHTML = w.join(" ") + "\n";
    const first = new Date(y, m, 1).getDay();
    let row = Array(first).fill("  ");
    const last = new Date(y, m+1, 0).getDate();
    for(let i=1;i<=last;i++){
      row.push(i.toString().padStart(2, ' '));
      if(row.length === 7){
        cal.innerHTML += row.join(" ") + "\n";
        row = [];
      }
    }
    if(row.length) cal.innerHTML += row.join(" ");
  </script>
</body>
</html>

  • monospace 文字の幅がすべて同じなので列が揃う
  • white-space: pre JavaScriptの \n や " " をそのままHTML上に反映
  • padStart(2, ' ') 数字を2桁に揃える工夫(桁ずれ防止)
  • first = getDay() 月初の曜日を取得して、空白を調整
  • 何となく寂しい見栄えになります。何かスタイルを補いたくなります

3.gridを利用する案

この案をベースに、閏年自前計算と、スタイルの変化を試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>カレンダー</title>
  <style>
    #calendar {
      display: grid;
      grid-template-columns: repeat(7, 1fr);
      max-width: 400px;
    }
    #calendar div {
      text-align: center;
      padding: 4px;
    }
  </style>
</head>
<body>
  <h1 id="today"></h1>
  <div id="calendar"></div>
  <script>
    const today = new Date(), y = today.getFullYear(), m = today.getMonth(), d = today.getDate();
    document.getElementById("today").textContent = `${y}/${m + 1}/${d}`;
    const cal = document.getElementById("calendar"), w = ["","","","","","",""];
    cal.innerHTML = w.map(v=>`<div>${v}</div>`).join("");
    const first = new Date(y, m, 1).getDay(), last = new Date(y, m + 1, 0).getDate();
    for(let i=0;i<first;i++) cal.innerHTML += `<div></div>`;
    for(let i=1;i<=last;i++) cal.innerHTML += `<div>${i}</div>`;
  </script>
</body>
</html>

  • HTML構成

    • <h1 id="today">
      → JavaScriptで今日の日付(yyyy/mm/dd)を表示するための要素。

    • <div id="calendar">
      → カレンダー(曜日と日付)をすべてグリッド表示するコンテナ。

  • CSSスタイル

    • #calendar に display: grid を設定
      → 7列(曜日)で整列表示するための基本設定。
    • grid-template-columns: repeat(7, 1fr)
      → 7列均等割りで曜日ごとの列幅を確保。
    • max-width: 400px
      → カレンダーの幅が広くなりすぎないよう制限。
    • #calendar div に text-align: center と padding
      → 日付や曜日を中央揃え&少し余白を確保して見やすくする。
  • JavaScriptの処理

    • const today = new Date()
      → 現在の日付情報を取得。
    • y, m, d
      → 年(y)、月(m)、日(d)をそれぞれ取得。
    • document.getElementById("today").textContent = ...
      → 取得した年月日を画面上部に表示。
    • const w = ["日", "月", ...]
      → 曜日を表す文字列を準備。
    • w.map(...).join("")
      → 曜日を <div> 要素としてカレンダーに挿入。
    • first = new Date(y, m, 1).getDay()
      → 今月1日の**曜日(0〜6)**を取得。
    • last = new Date(y, m+1, 0).getDate()
      → 今月の**最終日(日数)**を取得。
    • for (let i = 0; i < first; i++)
      → 月初までの空白セルを挿入(例:1日が水曜なら空白が2つ必要)。
    • for (let i = 1; i <= last; i++)
      → 実際の日付を <div> に1日ずつ入れてカレンダーを完成。

3-1.閏年を自前で計算する案

見栄えは変えずに、判定ロジックを変化させてみます。

$ diff this_month_grid\index.html this_month_grid_self_leap_chk\index.html -u
--- "this_month_grid\\index.html"       2025-04-06 22:28:13.800090600 +0900
+++ "this_month_grid_self_leap_chk\\index.html" 2025-04-07 06:45:44.106784600 +0900
@@ -23,7 +23,27 @@
     document.getElementById("today").textContent = `${y}/${m + 1}/${d}`;
     const cal = document.getElementById("calendar"), w = ["日","月","火","水","木","金","土"];
     cal.innerHTML = w.map(v=>`<div>${v}</div>`).join("");
-    const first = new Date(y, m, 1).getDay(), last = new Date(y, m + 1, 0).getDate();
+
+    // ==== 自前で曜日計算 ====
+    function isLeapYear(y) {
+      return y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0);
+    }
+    function getDaysInMonth(y, m) {
+      if (m === 1) return isLeapYear(y) ? 29 : 28;
+      return [31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m];
+    }
+    function countDaysSince1970(y, m, d) {
+      let days = 0;
+      for (let yy = 1970; yy < y; yy++) {
+        days += isLeapYear(yy) ? 366 : 365;
+      }
+      for (let mm = 0; mm < m; mm++) {
+        days += getDaysInMonth(y, mm);
+      }
+      return days + (d - 1);
+    }
+    const first = (countDaysSince1970(y, m, 1) + 4) % 7;
+    const last = getDaysInMonth(y, m);
     for(let i=0;i<first;i++) cal.innerHTML += `<div></div>`;
     for(let i=1;i<=last;i++) cal.innerHTML += `<div>${i}</div>`;
   </script>

  • first(月初の曜日)を Date で取得する代わりに、自前で算出
  • 関数 isLeapYear, getDaysInMonth, countDaysSince1970 を追加
  • 見た目・CSS・HTML構造など他部分は変更せずdiffが取れるように、必要最低限の変更に留める

閏年について

  • 閏年の判定ルール(西暦)
    • 4で割り切れる年は閏年
    • ただし、100で割り切れる年は平年
    • ただし、400で割り切れる年はやっぱり閏年
return y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0);
条件 結果
年 % 400 === 0 閏年 1600, 2000, 2400
年 % 100 === 0 平年 1700, 1800, 1900
年 % 4 === 0 閏年 1996, 2004, 2020
上記以外 平年 2023, 2025
year % 4 === 0         // 4の倍数であること(まず候補になる)
&& (
  year % 100 !== 0     // 100の倍数でない = 閏年確定
  || year % 400 === 0  // 100の倍数でも400の倍数ならOK
)

閏年判定の背景(なぜこうなっている?)

地球の公転周期は、約365.2422日
→ 毎年0.2422日ずつズレる
そこで:

  • 4年に1回 = +1日(= +0.25×4)→ 補正される
  • でも 0.25日では多すぎる → 100年に1回抜く(補正しすぎ防止)
  • それでも 微妙に不足 → 400年に1回戻す
    こうして 365.2425日に近づけています。

3-2.ダークテーマの案

以降は見栄えを変化させていきます。

$ diff this_month_grid\index.html this_month_grid_dark\index.html -u
--- "this_month_grid\\index.html"       2025-04-06 22:28:13.800090600 +0900
+++ "this_month_grid_dark\\index.html"  2025-04-07 06:58:46.224252800 +0900
@@ -4,6 +4,12 @@
   <meta charset="UTF-8">
   <title>カレンダー</title>
   <style>
+    body {
+      background-color: #121212;
+      color: #ffffff;
+      font-family: sans-serif;
+      padding: 20px;
+    }
     #calendar {
       display: grid;
       grid-template-columns: repeat(7, 1fr);
@@ -12,6 +18,9 @@
     #calendar div {
       text-align: center;
       padding: 4px;
+      background-color: #1e1e1e;
+      border: 1px solid #333;
+      border-radius: 4px;
     }
   </style>
 </head>

項目 新(ダークテーマ)
背景 暗めのグレー(#121212)
文字色 白(#ffffff)
セル背景 なし ダークグレー(#1e1e1e)
セル枠 なし 黒寄りの線(#333)
フォント 標準 sans-serif 指定
セル角丸 なし 角丸4px

3-3.中央配置の案

$ diff this_month_grid_dark\index.html this_month_grid_dark_center\index.html -u
--- "this_month_grid_dark\\index.html"  2025-04-07 06:58:46.224252800 +0900
+++ "this_month_grid_dark_center\\index.html"   2025-04-07 07:07:59.916219500 +0900
@@ -5,19 +5,29 @@
   <title>カレンダー</title>
   <style>
     body {
+      margin: 0;
+      padding: 20px;
       background-color: #121212;
       color: #ffffff;
       font-family: sans-serif;
-      padding: 20px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: flex-start;
+      min-height: 100vh;
     }
     #calendar {
       display: grid;
       grid-template-columns: repeat(7, 1fr);
       max-width: 400px;
+      width: 100%;
+      gap: 4px;
+      margin-top: 20px;
     }
     #calendar div {
       text-align: center;
-      padding: 4px;
+      aspect-ratio: 1 / 1;
+      line-height: 2.5em;
       background-color: #1e1e1e;
       border: 1px solid #333;
       border-radius: 4px;

  • 日付(<h1>)が上部中央に配置
  • カレンダーはその下に中央寄せで表示
  • セルのサイズ・背景・色はそのまま

3-4.今日をハイライトする案

$ diff this_month_grid_dark_center\index.html this_month_grid_dark_center_highlight_today\index.html -u
--- "this_month_grid_dark_center\\index.html"   2025-04-07 07:07:59.916219500 +0900
+++ "this_month_grid_dark_center_highlight_today\\index.html"   2025-04-07 07:59:52.931849300 +0900
@@ -32,6 +32,12 @@
       border: 1px solid #333;
       border-radius: 4px;
     }
+    #calendar div.today {
+      background-color: #004444;
+      border: 2px solid #00ffcc;
+      font-weight: bold;
+      color: #ffffff;
+    }
   </style>
 </head>
 <body>
@@ -44,7 +50,11 @@
     cal.innerHTML = w.map(v=>`<div>${v}</div>`).join("");
     const first = new Date(y, m, 1).getDay(), last = new Date(y, m + 1, 0).getDate();
     for(let i=0;i<first;i++) cal.innerHTML += `<div></div>`;
-    for(let i=1;i<=last;i++) cal.innerHTML += `<div>${i}</div>`;
+    for(let i=1;i<=last;i++) {
+      const isToday = y === today.getFullYear() && m === today.getMonth() && i === today.getDate();
+      const cls = isToday ? ' class="today"' : '';
+      cal.innerHTML += `<div${cls}>${i}</div>`;
+    }
   </script>
 </body>
 </html>

  • セルの描画時に「今日」であればクラス追加
  • CSSで .today クラスにだけ装飾

4.100年カレンダーの案

最後は、好きな年月を見れるようにしてみます。

  • HTML構成(index.html)

    • calendar-container:全体を囲むラッパー(中央寄せ用)
    • .header:年・月のプルダウンと、前月・今日・次月ボタン
    • .weekdays:曜日見出し(「日〜土」)
    • #calendar:実際の日付を表示する領域(JSで動的に更新)
  • CSSスタイル(style.css)

    • body:ダークテーマ(背景黒+文字白)
    • .calendar-container:幅を400pxに制限し、中央に配置
    • .header:ボタンやセレクトボックスを横並びに整列
    • select:月・年の選択ボックスの色や背景を暗く統一
    • .today:今日の日付だけハイライト(明るい枠+背景色)
    • button:disabled:移動できないときのボタンを暗く非活性表示
    • .calendar-grid:7列のグリッドで日付を整列表示(gap付き)
    • .calendar-grid div:日付の各セルを正方形に(aspect-ratio: 1/1)
  • JavaScriptの動作(script.js)

    • 初期化処理
      • currentDate = new Date():最初に「今日」の日付を記憶
      • 年:1970年〜2069年をセレクトボックスに追加
      • 月:0〜11(表示は1月〜12月)をセレクトボックスに追加
    • カレンダー描画処理(updateCalendar())
      • 選択中の年・月から、1日の曜日と末日を取得
      • カレンダー表示をいったん空にする(再描画のため)
      • 月初の曜日に合わせて空白のセルを追加(1日が水曜なら2つ空欄)
      • 1日〜末日までセルを追加
      • 今日の日付は .today クラスを付与(スタイルで強調)
    • ナビゲーション操作
      • 「←」ボタン:前月へ(1月なら前年の12月に切り替え)
      • 「→」ボタン:次月へ(12月なら翌年の1月に切り替え)
      • 「Today」ボタン:現在の年・月に戻る
      • セレクト変更時も、カレンダーを即更新
    • ボタンの無効化制御(updateNavigationButtons())
      • 1970年1月より前に戻れない → 「←」無効
      • 2069年12月より先に進めない → 「→」無効

0
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?