Calendar その2 - Javascript

  • 作成日:
  • 最終更新日:2026/01/17

カレンダーの作成

コード

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./style.css">
  <script src="./script.js"></script>
</head>
<body>
  <h2 id="month"></h2>
  <table id="calendar">
    <thead>
      <tr>
        <th>日</th><th>月</th><th>火</th><th>水</th>
        <th>木</th><th>金</th><th>土</th>
      </tr>
    </thead>
    <tbody></tbody>
  </table>
</body>
</html>

script.js

window.onload = function() {
  const today = new Date(2026, 3); // 2026年4月のデータ作成

  const year = today.getFullYear(); // 2026
  const month = today.getMonth(); // 4

  const monthTitle = document.getElementById('month');
  const tbody = document.querySelector('#calendar tbody');

  const holidays = {
    "2026": [
      { date: '2026-01-01', name: '元日' },
      { date: '2026-01-12', name: '成人の日' },
      { date: '2026-02-11', name: '建国記念の日' },
      { date: '2026-02-23', name: '天皇誕生日' },
      { date: '2026-03-20', name: '春分の日' },
      { date: '2026-04-29', name: '昭和の日' },
      { date: '2026-05-03', name: '憲法記念日' },
      { date: '2026-05-04', name: 'みどりの日' },
      { date: '2026-05-05', name: 'こどもの日' },
      { date: '2026-05-06', name: '振替休日' },
      { date: '2026-07-20', name: '海の日' },
      { date: '2026-08-11', name: '山の日' },
      { date: '2026-09-21', name: '敬老の日' },
      { date: '2026-09-22', name: '振替休日' },
      { date: '2026-09-23', name: '秋分の日' },
      { date: '2026-10-12', name: 'スポーツの日' },
      { date: '2026-11-03', name: '文化の日' },
      { date: '2026-11-23', name: '勤労感謝の日' }
    ]
  }

  
  const yearStr = String(year); // 表示中の年

  // 「書きやすさ・読みやすさ」のために Map を使用
  // 年ごとに一度だけ作る
  const holidayMap = new Map(
    holidays[yearStr]?.map(h => [h.date, h.name]) ?? []
  );

  // 表示月タイトル
  monthTitle.textContent = `${year}年 ${month + 1}月`;

  // 今月1日の曜日
  const firstDay = new Date(year, month, 1).getDay(); // 3

  // 今月の日数
  const lastDate = new Date(year, month + 1, 0).getDate(); // 30

  // 前月の日数
  const prevLastDate = new Date(year, month, 0).getDate(); // 31

  let row = document.createElement('tr');

  //
  // ① 前月の日付
  //
  for (let i = firstDay - 1; i >= 0; i--) {
    const cell = document.createElement('td');
    cell.textContent = prevLastDate - i;
    cell.classList.add('other-month');
    row.appendChild(cell);
  }

  //
  // ② 当月の日付
  //
  for (let day = 1; day <= lastDate; day++) {
    const cell = document.createElement('td');
    cell.textContent = day;

    const dateKey = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;

    // 祝日判定
    if (holidayMap.has(dateKey)) {
      cell.classList.add('holiday');
      cell.title = holidayMap.get(dateKey); // ホバーで祝日名表示
    }

    const weekDay = (firstDay + day - 1) % 7;

    // 日曜・土曜の色分け
    if (weekDay === 0) {
      cell.classList.add('sunday');
    } else if (weekDay === 6) {
      cell.classList.add('saturday');
    }

    // 今日の強調
    if (
      year === today.getFullYear() &&
      month === today.getMonth() &&
      day === today.getDate()
    ) {
      cell.classList.add('today');
    }

    row.appendChild(cell);

    if ((firstDay + day) % 7 === 0) {
      tbody.appendChild(row);
      row = document.createElement('tr');
    }
  }

  //
  // ③ 翌月の日付
  //
  // 翌月の1日が何曜日か?
  const endDay = (firstDay + lastDate) % 7; // 5
  let nextMonthDay = 1;

  // 最終日の次の日が日曜日以外ならtdタグを追加
  if (endDay !== 0) {
    for (let i = endDay; i < 7; i++) {
      const cell = document.createElement('td');
      cell.textContent = nextMonthDay++;
      cell.classList.add('other-month');
      row.appendChild(cell);
    }
  }

  // 最終行を追加
  tbody.appendChild(row);

}
CSS

style.css

#month {
  text-align: center;
}
table {
  border-collapse: collapse;
  width: 350px;
}
th, td {
  border: 1px solid #ccc;
  text-align: center;
  padding: 8px;
}
th {
  background: #f0f0f0;
}
.other-month {
  color: #aaa;
}
.today {
  background: #ffeb3b;
}
.holiday {
  color: red;
  font-weight: bold;
}
.sunday {
  color: red;
}
.saturday {
  color: blue;
}
#calendar {
  margin: 0 auto;
}
.calendar-header {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-bottom: 10px;
}
.calendar-header #yearSelect {
  font-size: 22px;
  padding: 2px;
}
.calendar-header #monthSelect {
  font-size: 22px;
  padding: 2px;
}

曜日番号

getDay()で取得した値の曜日は以下になります。

  • 0:日
  • 1:月
  • 2:火
  • 3:水
  • 4:木
  • 5:金
  • 6:土

① 前月の日付

「 firstDay - 1 」で2026年3月末日の曜日を取得します。

2026年4月1日が「 水曜日 」のため、firstDay に入る値は3です。そのため「 firstDay - 1 」で2026年3月末日の曜日を取得することができます。

「 firstDay - 1 」は2のため、2026年3月末は「 火曜日です。 」

for (let i = firstDay - 1; i >= 0; i--) {
  const cell = document.createElement('td');
  cell.textContent = prevLastDate - i;
  cell.classList.add('other-month');
  row.appendChild(cell);
}

上記のコードは、変数iは2から始まり、デクリメントのため0になるまで繰り返します。そのため実行される回数は「 3回 」です

2026年3月の日数は変数prevLastDateに代入されています。日数は31日です。

最初のループの際に「 cell.textContent 」で挿入される文字は「 31 - 2 」のため「 29 」です。

ループがデクリメントのためループの際、「 31 - 2 」、「 31 - 1 」、「 31 - 0 」と引く数が減るため前月の日付が取得できます。

② 当月の日付

1日から月末までループで処理します。ループの主な目的はtd要素に日付のテキストを挿入することです。「 row.appendChild(cell) 」の部分です。

土曜日になった場合の処理を以下のコードで処理ています。

if ((firstDay + day) % 7 === 0) {
  tbody.appendChild(row);
  row = document.createElement('tr');
}

上記のコードは、土曜日になれば、変数tbodyに変数rowの値を最後に挿入します。挿入した後、変数rowに「 document.createElement('tr') 」で新たに要素を作成し、空のtr要素を代入しなおします。

const dateKey = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;

// 以下のデータが作成されます
2026-04-01

padStart()は、第1引数で指定した文字の長さで第2引数の文字を繰り返します。

③ 翌月の日付

翌月の処理を行っているのは、以下のコードです。

const endDay = (firstDay + lastDate) % 7; // 5
let nextMonthDay = 1;

 if (endDay !== 0) {
  for (let i = endDay; i < 7; i++) {
    const cell = document.createElement('td');
    cell.textContent = nextMonthDay++;
    cell.classList.add('other-month');
    row.appendChild(cell);
  }
}

firstDay + lastDate の結果は「 3 + 30 = 33 」です。翌月1日まで曜日を進めた量を求めることができます。

その値を7で割ったあまりが2026年5月1日の曜日になります。

※以下の画像の日付下の赤文字が曜日の量になります。

翌月1日が日曜日以外ならtd要素を挿入するようにします。

2026年5月1日は金曜日のため、変数endDayは5です。そのためtd要素は2つ挿入されます。

翌月のスタート日は1日からになるため変数nextMonthDay に1を代入しおき、ループごとにインクリメントし日付のテキストを挿入します。