JavaScript
アイキャッチ 猫

こんにちは!
今回はJavaScriptでカレンダーを作成してみました。
カレンダー作成にはライブラリも存在しますが、勉学のために作成する方が多いかと思いますので今回は使用していません。自作すると、カスタマイズが容易であることもメリットですね!

はじめに

まずはカレンダーに持たせる機能を決定します。
今回は以下の機能を持たせることにしました。

  • ヘッダーに年月を表示
  • ボタンで月の移動ができる
  • 余白に先月、翌月の日付を灰色で表示
  • 本日の日付に色をつける

実際に作成したものですが、イメージ図はこちらです。

カレンダー作成イメージ図

また、次以降の投稿で祝日、スケジュール入力の機能を追加したいと考えています。

フロー(箇条書き)

次に箇条書きで処理フローを記述します。

  1. 日付オブジェクトを取得
  2. 年月を表示
  3. カレンダーを作成
    1. 曜日の行を作成
    2. 1日が何曜日からはじまるか取得
    3. 当月が何行になるか算出
    4. 月曜日から2. で取得した行数分日付を設定
  4. カレンダーを表示

こちらで実際にコーディングを進めていきます。

HTML

まずはHTMLを作成していきます。
作成しているのは、以下の3つです。

  1. 年月表示のヘッダー
  2. 月移動のボタン
  3. カレンダー
<div class="wrapper">
    <!-- xxxx年xx月を表示 -->
    <h1 id="header"></h1>

    <!-- ボタンクリックで月移動 -->
    <div id="next-prev-button">
        <button id="prev" onclick="prev()">‹</button>
        <button id="next" onclick="next()">›</button>
    </div>

    <!-- カレンダー -->
    <div id="calendar"></div>
</div>

CSS

次にCSSでスタイルを決定します。
この辺りは好きなようにカスタマイズをしていただいて良いと思います!

ただ、JavaScriptで以下にクラスを設定しているので、ご注意ください。

先月、翌月の日付:disabled
当日:today

@charset "utf-8";

/*全体*/
.wrapper{
    max-width: 600px;
    margin: 0 auto;
    color: #666;
}
#header {
    text-align: center;
    font-size: 24px;
    width: 100%;
    margin: 1rem 0 0;
}

/*カレンダー*/
#calendar {
    text-align: center;
    width: 100%;
}
table {
    border-collapse: collapse;
    width: 100%;
}
th {
    color: #000;
}
th, td {
    border: 1px solid #ddd;
    padding-top: 10px;
    padding-bottom: 10px;
    text-align: center;
}
/*日曜日*/
td:first-child {
    color: red;
}
/*土曜日*/
td:last-child {
    color: blue;
}
/*前後月の日付*/
td.disabled {
    color: #ccc;
}
/*本日*/
td.today {
    background-color: #D65E72;
    color: #fff;
}

/*ボタン*/
#next-prev-button {
    position: relative;
}
#next-prev-button button{
    cursor: pointer;
    background: #B78D4A;
    color: #fff;
    border: 1px solid #B78D4A;
    border-radius: 4px;
    font-size: 1rem;
    padding: 0.5rem 2rem;
    margin: 1rem 0;
}
#next-prev-button button:hover{
    background-color: #D4BB92;
    border-color: #D4BB92;
}
#prev {
    float: left;
}
#next {
    float: right;
}

JavaScript

JavaScriptで実際にカレンダーを作成、表示していきます。

コード

次項で詳しく解説していますが、コメントでも多少記述しています。
コピペも可能です!

const week = ["日", "月", "火", "水", "木", "金", "土"];
const today = new Date();
// 月末だとずれる可能性があるため、1日固定で取得
var showDate = new Date(today.getFullYear(), today.getMonth(), 1);

// 初期表示
window.onload = function () {
    showProcess(today, calendar);
};
// 前の月表示
function prev(){
    showDate.setMonth(showDate.getMonth() - 1);
    showProcess(showDate);
}

// 次の月表示
function next(){
    showDate.setMonth(showDate.getMonth() + 1);
    showProcess(showDate);
}

// カレンダー表示
function showProcess(date) {
    var year = date.getFullYear();
    var month = date.getMonth();
    document.querySelector('#header').innerHTML = year + "年 " + (month + 1) + "月";

    var calendar = createProcess(year, month);
    document.querySelector('#calendar').innerHTML = calendar;
}

// カレンダー作成
function createProcess(year, month) {
    // 曜日
    var calendar = "<table><tr class='dayOfWeek'>";
    for (var i = 0; i < week.length; i++) {
        calendar += "<th>" + week[i] + "</th>";
    }
    calendar += "</tr>";

    var count = 0;
    var startDayOfWeek = new Date(year, month, 1).getDay();
    var endDate = new Date(year, month + 1, 0).getDate();
    var lastMonthEndDate = new Date(year, month, 0).getDate();
    var row = Math.ceil((startDayOfWeek + endDate) / week.length);

    // 1行ずつ設定
    for (var i = 0; i < row; i++) {
        calendar += "<tr>";
        // 1colum単位で設定
        for (var j = 0; j < week.length; j++) {
            if (i == 0 && j < startDayOfWeek) {
                // 1行目で1日まで先月の日付を設定
                calendar += "<td class='disabled'>" + (lastMonthEndDate - startDayOfWeek + j + 1) + "</td>";
            } else if (count >= endDate) {
                // 最終行で最終日以降、翌月の日付を設定
                count++;
                calendar += "<td class='disabled'>" + (count - endDate) + "</td>";
            } else {
                // 当月の日付を曜日に照らし合わせて設定
                count++;
                if(year == today.getFullYear()
                  && month == (today.getMonth())
                  && count == today.getDate()){
                    calendar += "<td class='today'>" + count + "</td>";
                } else {
                    calendar += "<td>" + count + "</td>";
                }
            }
        }
        calendar += "</tr>";
    }
    return calendar;
}

解説

先述したコードに対して細かく解説していきます!
結構長くなってしまったので、不要な方は飛ばしてください。

・グローバル変数の定義

const week = ["日", "月", "火", "水", "木", "金", "土"];

曜日を格納した配列を定義しています。
変更予定がないので、定数(const)で作成しています。

const today = new Date();
var showDate = new Date(today.getFullYear(), today.getMonth(), 1);

・本日の日付オブジェクトを定数で宣言
・表示する日付オブジェクトを宣言

表示する日付オブジェクトは初期化では”today”の年月+1日固定で作成します。

Q:なぜ”today”をそのまま使用しないか
A:前の月、次の月表示で”getMonth()”に+-1を行っていますが、末日だと先々月、もしくは翌々日になることがあります。

・初期表示

window.onload = function () {
    showProcess(today, calendar);
};

当日の日付オブジェクトを使用して、showProcess関数を呼び出して初期表示を行います。

window.onload = function () {・・・};

↑はHTMLが読み込まれた直後に呼び出される関数です。

・prev()、next()関数

// 前の月表示
function prev(){
    showDate.setMonth(showDate.getMonth() - 1);
    showProcess(showDate);
}

// 次の月表示
function next(){
    showDate.setMonth(showDate.getMonth() + 1);
    showProcess(showDate);
}

「<」クリックでprev()、「>」クリックでnext()の関数がそれぞれ呼び出されます。

prev()関数の場合はshowDateオブジェクトに1月分減算後、
next()関数の場合はshowDateオブジェクトに1月分加算後にshowProcess関数を呼び出してカレンダーを表示します。

・カレンダー表示 showProcess関数

引数:date(表示するDateオブジェクト)

var year = date.getFullYear();
var month = date.getMonth();
document.querySelector('#header').innerHTML = year + "年 " + (month + 1) + "月";

ヘッダー(id=”#header”)に年月を表示させます。
getMonth()は0始まりのため、1月の場合、0が取得されます。
そのため、3行目では (month + 1) で月を設定します。

var calendar = createProcess(year, month);
document.querySelector('#calendar').innerHTML = calendar;

createProcess関数でカレンダーを作成し、id=”#calendar”にカレンダーを設定します。

・カレンダー作成 createProcess関数

引数:year(年)、month(月)
戻り値:作成したカレンダー

// 曜日
var calendar = "<table><tr class='dayOfWeek'>";
for (var i = 0; i < week.length; i++) {
    calendar += "<th>" + week[i] + "</th>";
}
calendar += "</tr>";

定数”week”を使用してカレンダー上部に表示させる、曜日を設定します。

var count = 0;
var startDayOfWeek = new Date(year, month, 1).getDay();
var endDate = new Date(year, month + 1, 0).getDate();
var lastMonthEndDate = new Date(year, month, 0).getDate();
var row = Math.ceil((startDayOfWeek + endDate) / week.length);

日付を設定するのに必要な変数を定義します。
それぞれの変数は以下の内容を定義しています。

count:日付のカウント
startDayOfWeek:表示する月の1日の曜日
endDate:表示する月の末日
lastMonthEndDate:表示する先月の末日
row:カレンダー(日付部分)の行数

// 1行ずつ設定
for (var i = 0; i < row; i++) {
    calendar += "<tr>";
    // 1colum単位で設定
    for (var j = 0; j < week.length; j++) {
        if (i == 0 && j < startDayOfWeek) {
            // 1行目で1日まで先月の日付を設定
            calendar += "<td class='disabled'>" + (lastMonthEndDate - startDayOfWeek + j + 1) + "</td>";
        } else if (count >= endDate) {
            // 最終行で最終日以降、翌月の日付を設定
            count++;
            calendar += "<td class='disabled'>" + (count - endDate) + "</td>";
        } else {
            // 当月の日付を曜日に照らし合わせて設定
            count++;
            if(year == today.getFullYear()
              && month == (today.getMonth())
              && count == today.getDate()){
                calendar += "<td class='today'>" + count + "</td>";
            } else {
                calendar += "<td>" + count + "</td>";
            }
        }
    }
    calendar += "</tr>";
}

始めのfor文は行単位、2つ目のfor文はカラム(日付)単位で回します。

3行目、25行目ではtrタグで括っています。

6行目の条件は「1行目、かつ、1日の曜日未満」の場合に8行目の処理を行います。
その場合にクラス「disabled」を付与して日付を設定します。
計算式は例で実際に当てはめてみましょう。

例:2021/01
lastMonthEndDate = 31
startDayOfWeek = 5 (金曜日)
日曜日:31 – 5 + 0 + 1 = 27
月曜日:31 – 5 + 1 + 1 = 28

9行目の条件は当月の最終日以降、翌月の日付を設定します。
上記同様、クラス「disabled」を付与して日付を設定します。

それ以外の13行目~23行目の処理は当月の日付1日から末日まで入ります。
また、16行目~18行目の条件は本日の日付かどうかチェックしており、本日の場合はクラス「today」を付与しています。

デモ

こちらがデモページになります。
初期表示は当月で表示され、本日が赤く表示されていると思います。

また、<>のボタンをクリックすることで月の移動ができますので、お試しください♪

最後に

いかがでしたでしょうか。
ライブラリを使わなくても意外と簡単に実装ができました!

良ければ他の投稿もご覧ください!

※ 2021/01/08:monthの設定方法を修正