カレンダーアプリ案を幾つか
春なのでカレンダーを作ってみました。
取り敢えず新人への課題や、新しい言語に触れた時のお題になるような気がしています。
- 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
→ 日付や曜日を中央揃え&少し余白を確保して見やすくする。
- #calendar に display: grid を設定
-
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日ずつ入れてカレンダーを完成。
- const today = new Date()
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月より先に進めない → 「→」無効
- 初期化処理