Help us understand the problem. What is going on with this article?

JavaScript カレンダー

前回作成した祝日クラスを使って、サンプルとしてカレンダーを作ってみました。
飾りっ気はありませんが、テーブルタグは使わずCSSでレイアウトしているのを活かして通常のグリッド表示のほかに1行表示やncalコマンド風の縦グリッド表示もできるようにしてみました。

01.jpg
02.jpg

HTML

index.html
<!DOCTYPE html>
<html lang='ja'>
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='user-scalable=no,width=device-width,initial-scale=1'>
        <title>Calendar</title>
        <script src='./calendar.js'></script>
        <script src='./holiday_class.js'></script>
        <link rel='stylesheet' href='./calendar.css'>
    </head>

    <body>

        <div class='input'>
            <input id='year' type='text' value='' onchange='calendar(this.value)'><input id='prev' type='button' value='-' onclick='prev()'>
            <input id='reset' type='button' value='Reset' onclick='reset()'>
            <input id='next' type='button' value='+' onclick='next()'>
            <div class='selector'>
                開始曜日
                <select id='startWeek' onchange="calendar(document.querySelector('#year').value);checkUseStorage()"></select>
                表示タイプ
                <select id='displayType' onchange='displayType(this.value);checkUseStorage()'>
                    <option value='0'>Grid</option>
                    <option value='1'>Line</option>
                    <option value='2'>Vertical</option>
                    <option value='3'>VGrid</option>
                </select>
            </div>
        </div>

        <div class='year'></div>

        <div class='month m1'></div>
        <div class='month m2'></div>
        <div class='month m3'></div>
        <div class='month m4'></div>
        <div class='month m5'></div>
        <div class='month m6'></div>
        <div class='month m7'></div>
        <div class='month m8'></div>
        <div class='month m9'></div>
        <div class='month m10'></div>
        <div class='month m11'></div>
        <div class='month m12'></div>

        <div id='m'></div>
    </body>
</html>

JavaScript

calendar.js
// Dateオブジェクト
const d = new Date();

// 今日の年月日
const today = {
    year: d.getFullYear(),
    month: d.getMonth() + 1,
    day: d.getDate(),
};

// 曜日ラベル
const weekLabel = ['', '', '', '', '', '', ''];

// カレンダー表示
function calendar(year = 0) {

    if(isNaN(year) || year < 1 || year > 9999 || year != Math.floor(year))
        year = today.year;
    else
        year = Number(year);

    // 祝日リスト取得
    const holiday = new Holiday(year).getHolidayOfYear();

    // 年
    document.querySelector('.year').innerHTML = `<div class='yearName'>${year}年</div>`;
    document.querySelector('#year').value = year;

    // 週開始曜日
    const startWeek = document.querySelector('select#startWeek') ? 
        Number(document.querySelector('select#startWeek').value) : 0;

    // 月ループ
    for(let month = 1; month <= 12; month++) {
        // 月ラベル
        let monthBlock = `<div class='monthName'>${month}月</div>`;

        // 曜日ラベル
        monthBlock += "<div class='weekTop weekLabel'>";
        for(let i = 0; i < 7; i++) {
            const w = (startWeek + i) % 7;
            const weekClass = w === 0 ? ' sun' : w === 6 ? ' sat' : '';
            monthBlock += `<div class='d${weekClass}'><div class='t'>${weekLabel[w]}</div></div>`;
        }
        monthBlock += '</div>';

        // 当月1日の曜日
        d.setFullYear(year, month -1, 1);
        const firstDayWeek = d.getDay();

        // 月初余白
        if(firstDayWeek != startWeek) {
            monthBlock += "<div class='weekTop'>" +
                ("<div class='d'>&nbsp;</div>".repeat((firstDayWeek - startWeek + 7) % 7));
        }

        let wCount = 0;

        // 当月最終日
        d.setFullYear(year, month, 0);
        const lastDay = d.getDate();

        // 日ループ
        for(let day = 1; day <= lastDay; day++) {
            // 曜日取得
            d.setFullYear(year, month - 1, day);
            const w = d.getDay();

            // 土日クラス名
            let weekClass = w === 0 ? ' sun' : w === 6 ? ' sat' : '';
            // 祝日クラス名
            if(holiday[month][day] !== undefined) weekClass += ' holiday';

            // 週初め
            if(w === startWeek) monthBlock += "<div class='weekTop'>";

            // 祝日名表示用
            const holidayTag = (holiday[month][day] !== undefined) ?
                "<span class='holiday' " +
                    `onmousemove='p1("${month}/${day} ${holiday[month][day]}")' onmouseout='p0()'>` :
                '';
            monthBlock += holidayTag;

            // 当日クラス名
            const todayClass =
                (today.year === year && today.month === month && today.day === day) ?
                ' today' : '';

            // 当日日付
            monthBlock += `<div class='d${weekClass}${todayClass}'><div class='t'>${day}</div></div>`;

            // 祝日閉じ
            monthBlock += (holidayTag !== '') ? '</span>' : '';

            // weekTopの閉じ
            if(w === (startWeek + 6) % 7 || day === lastDay) {
                monthBlock += '</div>';
                wCount++;
            }
        }

        if(wCount < 6)
            monthBlock += "<div class='weekTop'><div class='d'>&nbsp;</div></div>".repeat(6 - wCount);

        // DOM更新
        document.querySelector(`.month.m${month}`).innerHTML = monthBlock;
    }

    displayType(document.querySelector('#displayType').value);
}

function p1 (str){
    const d = document.querySelector('#m');
    d.textContent = str;
    d.style.top = (y > 0 ? y : 0) + 'px';
    d.style.left = (x > 0 ? x : 0) + 'px';
    d.style.display = 'block';
}

function p0 (){
    const d = document.querySelector('#m');
    d.style.display = 'none';
}

let x, y;
const storage = getStorage('calendar_params');

onload = function() {
    let startWeekSelectOptions = '';

    for(let i = 0; i < 7; i++)
        startWeekSelectOptions += `<option value='${i}'>${weekLabel[i]}</option>`;

    if(document.querySelector('select#startWeek'))
        document.querySelector('select#startWeek').innerHTML = startWeekSelectOptions;

    if(storage['startWeek'] !== undefined)
        document.querySelector('select#startWeek').value = storage['startWeek'];

    if(storage['displayType'] !== undefined)
        document.querySelector('select#displayType').value = storage['displayType'];

    calendar();

    document.onmousemove = function(e) {
        x = e.pageX - 40;
        y = e.pageY - 40;
    }
    document.ontouchstart = function(e) {
        const ct = e.changedTouches[0];
        x = ct.pageX - 40;
        y = ct.pageY - 72;
    }

    checkUseStorage();
    setInterval(checkDayUpdate, 1000);
}

// 日付切替わり時更新
function checkDayUpdate() {
    const nd = new Date();
    if(today.day != nd.getDate()) {
        const prevDayYear = today.year;
        today.year = nd.getFullYear();
        today.month = nd.getMonth() + 1;
        today.day = nd.getDate();
        if(document.querySelector('#year').value == prevDayYear) {
            calendar(today.year);
        }
    }
}

function prev() {
    calendar(Number(document.querySelector('#year').value) - 1);
}
function next() {
    calendar(Number(document.querySelector('#year').value) + 1);
}
function reset() {
    calendar(today.year);
}

// レイアウト変更時 クラス名追加/削除
function displayType(n) {
    function classNameReplace(e, arr, n) {
        for(const i in arr) if(arr[i]) e.classList.remove(arr[i]);
        if(arr[n]) e.classList.add(arr[n]);
    }

    for(const i of document.querySelectorAll('.month'))
        classNameReplace(i, ['', 'line', 'v', 'gv'], n);

    for(const i of document.querySelectorAll('.weekLabel'))
        classNameReplace(i, ['', 'line', 'line', ''], n);

    for(const i of document.querySelectorAll('.monthName'))
        classNameReplace(i, ['', 'line', 'v', 'gv'], n);

    for(const i of document.querySelectorAll('div.d'))
        classNameReplace(i, ['', '', 'v', 'gv'], n);

    for(const i of document.querySelectorAll('.weekTop'))
        classNameReplace(i, ['', '', '', 'gv'], n);
}

// storage保存
function setStorage(name, value) {
    localStorage.setItem(name, JSON.stringify(value));
}

// storage取得
function getStorage(name) {
    const storage = localStorage.getItem(name);
    return storage ? JSON.parse(storage) : {};
}

// レイアウト保持状態チェック
function checkUseStorage() {
    if(+document.querySelector('select#startWeek').value || +document.querySelector('select#displayType').value)
        updateStorage();
    else
        deleteStorage();
}

// レイアウト保持storage更新
function updateStorage() {
    setStorage('calendar_params', {
            'startWeek': document.querySelector('select#startWeek').value,
            'displayType': document.querySelector('select#displayType').value,
        }
    );
}

// レイアウト保持storage削除
function deleteStorage() {
    localStorage.removeItem('calendar_params');
}

CSS

calendar.css
input[type="text"] {
    width: 48px;
}
input[type="button"] {
    width: 25px;
}
input[type="button"]#reset {
    width: 52px;
}
select {
    font-size: 11px;
    height: 20px;
}
.input {
    padding-bottom: 10px;
}
.selector {
    display: inline-block;
    border: 1px #e0e0e0 inset;
    padding: 4px;
    border-radius: 3px;
    font-size: 12px;
    white-space: nowrap;
}
span.holiday {
    text-decoration: none;
    cursor: pointer;
}
.year {
    padding-bottom: 16px;
}
.yearName {
    margin: 0 auto 0 auto;
}
.month {
    line-height: 150%;
    padding-bottom: 16px;
    display: inline-grid;
    padding-right: 16px;
}
.monthName {
    margin: 0 auto 0 auto;
}
div.d {
    display: inline-grid;
    width: 26px;
}
.t {
    margin: 0 auto 0 auto;
    font-size: 14px;
}
.d.sat {
    color: #4444ff;
}
.d.sun {
    color: #ff4444;
}
.d.holiday {
    color: #ff4444;
    font-weight: bold;
}
.d.today {
    background: #ddddff;
}
#m {
    position: absolute;
    top: 0px;
    left: 0px;
    display: none;
    padding: 4px 12px 4px 12px;
    background-color: #ffffff;
    color: #882222;
    font-size: 14px;
    font-weight: bold;
    border: 1px #884444 solid;
}

/* 横一列レイアウト */
.month.line {
    display: -webkit-inline-box;
    width: fit-content;
    border-top: 1px #e0e0e0 solid;
}
.weekLabel.line {
    display: none;
}
.monthName.line {
    width: 48px;
    text-align: right;
    padding-right: 16px;
    display: inline-grid;
}

/* 縦一列レイアウト */
div.d.v {
    display: block;
    text-align: center;
}
.month.v {
    line-height: 140%;
    padding-right: 0px;
    width: 40px;
    border-right: 1px #e0e0e0 solid;
    margin-bottom: 24px;
}
.monthName.v {
    margin: 0;
}

/* 縦グリッドレイアウト */
div.d.gv {
    display: flex;
}
.month.gv {
    display: inline-flex;
    padding-right: 0px;
}
.monthName.gv {
    position: absolute;
    padding-left: 22px;
}
.weekTop.gv {
    padding-top: 25px;
}

他に前回の祝日クラスholiday_class.jsとして保存したファイルを使います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away