読み込み中...

JavaScriptでカレンダーを作成!サンプルコード10選で簡単理解

JavaScriptカレンダーのサンプルコード例 JS
この記事は約26分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

JavaScriptでカレンダーを作るなら、Dateで年月日を扱い、tableまたはdivで日付セルを並べ、addEventListenerで操作を加える構成が扱いやすいです。基本の作り方を押さえると、月送り、予定表示、週表示、ToDo、国際化まで同じ考え方で広げられます。

動作確認環境
  • HTML Living Standard / CSS Cascading and Inheritance Level 5
  • JavaScript ECMAScript 2024 相当
  • Google Chrome 126 / Mozilla Firefox 127 / Microsoft Edge 126

そのため、カレンダーのサンプルコードは「日付を計算する部分」と「画面へ描画する部分」を分けて読むと理解しやすくなります。公式仕様に近い挙動は、MDNのDateDocument.createElement()を参照すると確認できるのが基本です。

📖 この記事で学べること
  • JavaScriptカレンダーの基本構造と作り方
  • Dateを使った月の日数と曜日の求め方
  • 月移動、予定、週表示、年間表示のサンプルコード
  • レスポンシブ対応とカスタムデザインの考え方
  • タイムゾーン、XSS、DOM更新でつまずきやすい点

JavaScriptカレンダーの基本

基本的に、JavaScriptカレンダーは「年月を決める」「月初の曜日を求める」「月末日までセルを作る」という流れで組み立てます。この作り方では、new Date(year, month, 1)で月初、new Date(year, month + 1, 0)で月末を表せる点が中核になります。

このとき、JavaScriptの月は0から始まるため、1月は0、12月は11です。初心者がつまずきやすいのは、画面に出す月番号とDateへ渡す月番号を同じものとして扱ってしまう点でしょう。

カレンダーの表示には、曜日と日付の格子構造を自然に表せる<table>がよく使われますし、ここがポイントです。一方、カード型の予定表やモバイル中心のUIでは<div>display: gridを組み合わせる作り方も現実的です。

要素主な役割使うAPIや要素注意点
年月表示対象の月を決めるDate, getFullYear(), getMonth()getMonth()は0始まり
月の日数日付セルの上限を決めるnew Date(year, month + 1, 0), getDate()うるう年はDateが処理する
曜日月初の空白セルを決めるgetDay()日曜が0
描画先カレンダーを挿入するgetElementById(), appendChild()大量更新では差し替え範囲を絞る
予定日付に情報を紐づけるdata-date, querySelector()ユーザー入力はtextContentで扱う
月移動前月・翌月へ切り替えるaddEventListener(), click年またぎを処理する
見た目幅、余白、色を整えるborder-collapse, padding, background-color小画面では文字量を抑える
国際化言語や曜日表記を変えるIntl.DateTimeFormat, select週の開始曜日は地域差がある

カレンダーのHTML構造

カレンダーのHTML構造は、曜日行を<thead>、日付行を<tbody>に分けると読みやすくなります。その構成なら、スクリーンリーダーやCSSによる装飾でも意味が崩れにくいです。

具体的には、曜日名を<th scope="col">に置き、日付を<td>へ配置します。この形にしておくと、後からdata-dateclassListで予定、選択状態、週末色を加えやすくなるのが目安です。

💡 Tips: 予定を後から差し込む作り方では、日付セルにdata-date="2026-06-12"のような属性を持たせると検索しやすくなるのが基本です。

JavaScriptでのカレンダー生成

JavaScriptでの生成処理は、forで日付を進めながらセルを追加する形が素直です。ただし、文字列を連結してinnerHTMLへ入れる作り方と、createElement()でDOMを作る作り方では安全性と拡張性に違いがあります。

一般に、固定のHTMLだけを組み立てるならinnerHTMLでも短く書けるのがポイントです。一方、ユーザー入力や外部データを扱うカレンダーでは、textContentappendChild()を使うほうが安全に整理できます。

関連するイベント処理を深めたい場合は、JavaScriptイベント徹底解説!30個の使い方と応用例も参考になるのが目安です。クリック、変更、キー入力を分けて理解すると、カレンダーUIの作り方が整理しやすくなるでしょう。

カレンダー作成のための10選サンプル

カレンダー作成のサンプルコードは、小さく動く単位に分けると改修しやすくなるのが一般的です。ここからは、基本表示から国際化まで、同じ考え方を少しずつ広げる形で確認します。

サンプルコード1:基本的なカレンダー作成

このサンプルコードは、現在の年月を読み取り、その月の日付を表形式で並べます。作り方の軸は、月初の曜日ぶんだけ空白セルを作り、月末日まで数字を追加することです。

<div id="calendar"></div>

結果: 期待される表示は、カレンダーを挿入する空の領域です。

const calendarEl = document.getElementById('calendar');
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth();
const weekLabels = ['日', '月', '火', '水', '木', '金', '土'];
const firstDay = new Date(year, month, 1).getDay();
const lastDate = new Date(year, month + 1, 0).getDate();

let html = '<table><thead><tr>';
weekLabels.forEach((label) => {
  html += `<th scope="col">${label}</th>`;
});
html += '</tr></thead><tbody><tr>';

for (let blank = 0; blank < firstDay; blank++) {
  html += '<td></td>';
}

for (let date = 1; date <= lastDate; date++) {
  const day = new Date(year, month, date).getDay();
  html += `<td data-date="${date}">${date}</td>`;
  if (day === 6 && date !== lastDate) {
    html += '</tr><tr>';
  }
}

html += '</tr></tbody></table>';
calendarEl.innerHTML = html;

結果: 期待される表示は、現在月の日付が日曜始まりの表で並ぶカレンダーです。

そのコードでは、firstDayが月初の曜日、lastDateが月末日を表します。日付セルへdata-dateを持たせているため、後から予定や選択状態を付けるサンプルコードへ広げやすい構成です。

サンプルコード2:月ごとのカレンダー切り替え

月ごとの切り替えでは、表示中のyearmonthを状態として持ち、ボタン操作で値を更新します。この作り方なら、12月から翌年1月、1月から前年12月への移動も一か所で処理できるのが現実的です。

<button id="prevMonth" type="button">前の月</button>
<button id="nextMonth" type="button">次の月</button>
<div id="calendar"></div>

結果: 期待される表示は、前月と翌月へ移動するボタンとカレンダー領域です。

let displayYear = new Date().getFullYear();
let displayMonth = new Date().getMonth();

function moveMonth(diff) {
  displayMonth += diff;

  if (displayMonth < 0) {
    displayMonth = 11;
    displayYear -= 1;
  }

  if (displayMonth > 11) {
    displayMonth = 0;
    displayYear += 1;
  }

  renderCalendar(displayYear, displayMonth);
}

document.getElementById('prevMonth').addEventListener('click', () => moveMonth(-1));
document.getElementById('nextMonth').addEventListener('click', () => moveMonth(1));

結果: 期待される表示は、ボタンを押すたびに表示月が前後へ切り替わる動作です。

このとき、renderCalendar()はサンプルコード1の描画処理を関数化したものとして考えます。forEachの制御や途中終了の考え方を確認したい場合は、JavaScriptのforEachでreturnを活用する6つのテクニックが補助になります。

サンプルコード3:イベント情報を追加するカレンダー

予定付きカレンダーでは、日付とタイトルを持つ配列を用意し、該当する日付セルへ予定名を追加すると整理できるのがポイントです。ただし、ユーザーが入力した文字列を扱う場合は、innerHTMLではなくtextContentを使うのが基本です。

const events = [
  { date: '2026-06-05', title: '定例会' },
  { date: '2026-06-15', title: '提出期限' }
];

結果: 期待される出力は、日付文字列と予定名を持つ予定データの配列です。

function addEventsToCalendar(events) {
  events.forEach((event) => {
    const cell = document.querySelector(`[data-date-full="${event.date}"]`);
    if (!cell) return;

    const label = document.createElement('div');
    label.className = 'event-label';
    label.textContent = event.title;
    cell.appendChild(label);
  });
}

結果: 期待される表示は、該当する日付セルの中に予定名が追加された状態です。

その処理では、querySelector()で対象セルを探し、見つからない日付はreturnで処理を終えます。forEachmapの使い分けを確認するなら、JavaScriptエンジニア必見!forEach・mapの使い分け術10選が関連します。

サンプルコード4:週表示カレンダー

週表示カレンダーは、対象日の週だけを横一列で見せる形式です。月表示より情報量を絞れるため、日ごとの予定数やタスク数を見せたい画面で使いやすいでしょう。

<table id="weekCalendar">
  <thead>
    <tr>
      <th scope="col">日</th><th scope="col">月</th><th scope="col">火</th>
      <th scope="col">水</th><th scope="col">木</th><th scope="col">金</th><th scope="col">土</th>
    </tr>
  </thead>
  <tbody><tr id="weekDays"></tr></tbody>
</table>

結果: 期待される表示は、曜日見出しと一週間分の日付を入れる行を持つ表です。

const weekDaysEl = document.getElementById('weekDays');

function renderWeekCalendar(baseDate) {
  weekDaysEl.textContent = '';
  const start = new Date(baseDate);
  start.setDate(start.getDate() - start.getDay());

  for (let i = 0; i < 7; i++) {
    const day = new Date(start);
    day.setDate(start.getDate() + i);

    const cell = document.createElement('td');
    cell.textContent = `${day.getMonth() + 1}/${day.getDate()}`;
    weekDaysEl.appendChild(cell);
  }
}

renderWeekCalendar(new Date());

結果: 期待される表示は、対象日を含む週の日曜から土曜までの日付一覧です。

このサンプルコードでは、setDate()で日付を戻して週の開始日を求めています。週の開始を月曜にしたい場合は、getDay()の値を補正する関数を別に用意すると保守しやすくなります。

サンプルコード5:レスポンシブデザイン対応カレンダー

レスポンシブ対応では、表全体を画面幅に合わせつつ、セル内の文字が詰まりすぎないよう調整すると理解できるのが一般的です。カレンダーの作り方としては、width: 100%table-layout: fixed、短い曜日名を組み合わせると破綻を抑えられます。

<style>
  .calendar-table {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
  }

  .calendar-table th,
  .calendar-table td {
    border: 1px solid #d1d5db;
    padding: 8px 4px;
    text-align: center;
    vertical-align: top;
    min-height: 44px;
  }

  @media (max-width: 480px) {
    .calendar-table th,
    .calendar-table td {
      padding: 6px 2px;
      font-size: 0.85rem;
    }
  }
</style>

結果: 期待される表示は、画面幅に合わせてセル幅と文字サイズが調整されるカレンダーです。

そのCSSでは、border-collapseで罫線の重なりを整え、table-layout: fixedで列幅の揺れを抑えます。予定名が長い場合は、overflow-wrapや省略表示を追加すると小画面でも読みやすいです。

⚠️ 注意: スマートフォンで予定を多く表示する場合、日付セルへ全文を詰め込むと操作しづらくなります。日付をタップして詳細を別領域に出す設計も検討対象です。

サンプルコード6:カスタムデザインカレンダー

カスタムデザインでは、曜日、今日、週末、選択中の日付を見分けられる状態にすると覚えるとよいでしょう。装飾を増やすより、classList.add()で意味のあるクラスを付け、CSS側で一貫した見た目にするほうが扱いやすいです。

<style>
  .calendar-table th {
    background-color: #f3f4f6;
    color: #111827;
  }

  .calendar-table td.weekend {
    background-color: #f9fafb;
  }

  .calendar-table td.today {
    outline: 2px solid #2563eb;
    outline-offset: -2px;
    font-weight: 700;
  }

  .event-label {
    margin-top: 4px;
    font-size: 0.75rem;
    color: #1f2937;
  }
</style>

結果: 期待される表示は、週末や今日が視覚的に区別できるカレンダーです。

function decorateDayCell(cell, date) {
  const today = new Date();

  if (date.getDay() === 0 || date.getDay() === 6) {
    cell.classList.add('weekend');
  }

  if (
    date.getFullYear() === today.getFullYear() &&
    date.getMonth() === today.getMonth() &&
    date.getDate() === today.getDate()
  ) {
    cell.classList.add('today');
  }
}

結果: 期待される表示は、週末セルにweekend、今日のセルにtodayが付いた状態です。

この作り方では、判定ロジックと見た目の指定が分かれます。将来、祝日、選択中、締め切りなどの状態を足す場合も、classNameを直接上書きせずclassListで追加するほうが安全です。

サンプルコード7:年間カレンダー

年間カレンダーは、1月から12月までの小さな月表示を並べる構成です。月表示の関数を再利用できるようにしておくと、年間表示のサンプルコードは短く保てます。

<div id="yearCalendar" class="year-calendar"></div>

結果: 期待される表示は、12か月分のカレンダーを挿入する領域です。

const yearCalendarEl = document.getElementById('yearCalendar');

function renderYearCalendar(year) {
  yearCalendarEl.textContent = '';

  for (let month = 0; month < 12; month++) {
    const section = document.createElement('section');
    const heading = document.createElement('h4');

    heading.textContent = `${month + 1}月`;
    section.appendChild(heading);
    section.appendChild(createCalendarTable(year, month));
    yearCalendarEl.appendChild(section);
  }
}

renderYearCalendar(new Date().getFullYear());

結果: 期待される表示は、指定した年の1月から12月までを順番に並べた年間カレンダーです。

その構成では、createCalendarTable()が月ごとのHTMLTableElementを返す前提です。月表示を文字列で返す関数にしても構いませんが、予定や状態を後から追加するならDOM要素を返すほうが拡張しやすいと言えます。

サンプルコード8:ドラッグ&ドロップ機能付きカレンダー

ドラッグ&ドロップでは、移動元の予定要素にdraggableを付け、日付セル側でdragoverdropを受け取ります。マウス操作だけで予定を移せる一方、キーボード操作への代替UIも考える必要があると考えられますし、ここがポイントです。

<div class="event" draggable="true" data-title="会議">会議</div>
<div class="event" draggable="true" data-title="締め切り">締め切り</div>

結果: 期待される表示は、ドラッグできる予定ラベルが並んだ状態です。

document.querySelectorAll('.event').forEach((eventEl) => {
  eventEl.addEventListener('dragstart', (event) => {
    event.dataTransfer.setData('text/plain', eventEl.dataset.title);
  });
});

document.querySelectorAll('[data-date-full]').forEach((cell) => {
  cell.addEventListener('dragover', (event) => {
    event.preventDefault();
  });

  cell.addEventListener('drop', (event) => {
    event.preventDefault();
    const title = event.dataTransfer.getData('text/plain');
    const label = document.createElement('div');
    label.textContent = title;
    cell.appendChild(label);
  });
});

結果: 期待される表示は、予定ラベルを日付セルへドロップすると、そのセル内へ予定名が追加される動作です。

このサンプルコードでは、dataTransferへ文字列を入れて受け渡しています。イベントハンドラの整理を深めたい場合は、JavaScriptにおけるイベントハンドラを完全ガイド!20選の実践サンプルコード付きが役立ちます。

サンプルコード9:カレンダーにToDoリスト機能追加

ToDoリストを組み合わせると、日付と作業を同じ画面で扱えますが、これは押さえたい点です。小規模な作り方なら、入力欄、追加ボタン、一覧の<ul>を用意し、空文字を除外して<li>を作るだけで始められますが、これは押さえたい点です。

<input id="todoInput" type="text" placeholder="ToDoを追加">
<button id="addTodo" type="button">追加</button>
<ul id="todoList"></ul>

結果: 期待される表示は、ToDoの入力欄、追加ボタン、一覧領域です。

const todoInput = document.getElementById('todoInput');
const addTodo = document.getElementById('addTodo');
const todoList = document.getElementById('todoList');

function appendTodo() {
  const text = todoInput.value.trim();
  if (text === '') return;

  const item = document.createElement('li');
  item.textContent = text;
  todoList.appendChild(item);
  todoInput.value = '';
}

addTodo.addEventListener('click', appendTodo);
todoInput.addEventListener('keydown', (event) => {
  if (event.key === 'Enter') appendTodo();
});

結果: 期待される表示は、入力したToDoがリストへ追加され、追加後に入力欄が空になる動作です。

その処理では、trim()で空白だけの入力を除外しています。日付と紐づける場合は、選択中の日付をselectedDateとして保持し、ToDoデータにdateプロパティを持たせると管理しやすいです。

サンプルコード10:国際化対応カレンダー

国際化対応では、曜日名、月名、日付の並び、週の開始曜日を分けて考えます。曜日名だけなら配列で十分ですが、実運用に近い作り方ではIntl.DateTimeFormatを使うと地域差を扱いやすくなると言えるでしょう。

<select id="localeSelect">
  <option value="ja-JP">日本語</option>
  <option value="en-US">English</option>
</select>

結果: 期待される表示は、カレンダーの表示言語を選ぶセレクトボックスです。

const localeSelect = document.getElementById('localeSelect');

function getWeekdayLabels(locale) {
  const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short' });
  const baseSunday = new Date(Date.UTC(2026, 5, 7));

  return Array.from({ length: 7 }, (_, index) => {
    const date = new Date(baseSunday);
    date.setUTCDate(baseSunday.getUTCDate() + index);
    return formatter.format(date);
  });
}

localeSelect.addEventListener('change', (event) => {
  const labels = getWeekdayLabels(event.target.value);
  document.querySelectorAll('#calendar thead th').forEach((cell, index) => {
    cell.textContent = labels[index];
  });
});

結果: 期待される表示は、選択したロケールに応じて曜日見出しが切り替わるカレンダーです。

このサンプルコードでは、Date.UTC()で基準日を作り、Intl.DateTimeFormatで曜日名へ変換しています。公式ドキュメントによれば、Intl.DateTimeFormatは言語に応じた日付と時刻の整形に使えるAPIです。

注意点と対処法

カレンダー実装で特に押さえたいのは、タイムゾーン、うるう年、ユーザー入力、DOM更新の負荷です。見た目が正しくても、日付が1日ずれたり、入力文字列をHTMLとして解釈したりすると、予定管理の信頼性が下がります。

タイムゾーンでは、日付だけを扱いたいのか、時刻まで含めたいのかを分けて考えますし、これが一つの目安です。日付だけの予定ならYYYY-MM-DDの文字列で保存し、表示時に必要な形へ変換するほうが予期しないずれを避けやすいです。

一方、会議時刻のように時間帯が意味を持つ予定では、UTCで保存して表示時にローカル時刻へ変換する方法が一般的です。toISOString()getTimezoneOffset()Intl.DateTimeFormatの役割を混同しないように整理するのが現実的です。

うるう年については、new Date(year, 2, 0).getDate()で2月末日を求めると、28日か29日かをDateに任せられます。独自の判定式を書く作り方もありますが、通常の月末計算なら標準APIを使うほうが簡潔です。

ただし、予定名やToDoなどのユーザー入力を表示する場面では注意が必要です。innerHTMLへそのまま入れると、文字列がHTMLとして解釈される可能性があるため、表示だけならtextContentを使いるのが基本です。

⚠️ 注意: 外部データやユーザー入力をカレンダーへ入れる場合、innerHTMLの常用は避けます。HTMLを許可する設計なら、サニタイズ処理と許可タグの範囲を別途決める必要があると整理できます。

DOM更新の負荷は、日付セルや予定要素が増えるほど目立ちますが、覚えておくと役立つでしょう。大量の要素を一つずつ画面へ追加するより、DocumentFragmentへ組み立ててからまとめて差し込むほうが処理の見通しも良くなります。

const fragment = document.createDocumentFragment();

for (let date = 1; date <= 31; date++) {
  const cell = document.createElement('td');
  cell.textContent = String(date);
  fragment.appendChild(cell);
}

calendarEl.appendChild(fragment);

結果: 期待される表示は、作成した日付セルがまとめてカレンダー領域へ追加される状態です。

これにより、描画のたびにコード全体が読みにくくなる問題も抑えられます。拡張子つきのファイル分割を検討する場合は、JavaScriptで拡張子を活用する方法12選!初心者でも簡単にできる方法を紹介も関連知識になります。

アクセシビリティでは、曜日見出しにscope="col"を付け、ボタンには目的が分かるテキストを入れますし、ここを基本と考えるとよいでしょう。キーボード操作を想定するなら、日付セルへbuttonを入れる、選択状態をaria-pressedで示す、といった設計が考えられますし、これが一つの目安です。

そのため、カレンダーの作り方は表示だけで終わらせず、操作、保存、安全性まで同じ流れで確認すると安定します。小さなサンプルコードを積み重ねるほど、後から要件が増えても変更箇所を読み取りやすくなるでしょう。

まとめ

JavaScriptカレンダーは、Dateによる日付計算、HTMLによる表示構造、イベント処理による操作を組み合わせて作りますし、ここがポイントです。最初は月表示のサンプルコードから始め、月移動、予定追加、週表示、年間表示へ広げる作り方が理解しやすいです。

そのうえで、レスポンシブ対応、カスタムデザイン、ドラッグ&ドロップ、ToDo、国際化を加えると、用途に合わせたカレンダーへ発展します。どの機能でも、日付データと描画処理を分ける設計が保守性を支えますが、覚えておくと役立つでしょう。

ただし、タイムゾーン、うるう年、XSS、DOM更新は後回しにすると修正範囲が広がりやすい領域です。基本のサンプルコード段階からtextContentDocumentFragmentIntl.DateTimeFormatなどを適切に使うと、実用的なカレンダーへ近づきますが、これは押さえたい点です。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。