6
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScript カレンダー

Last updated at Posted at 2020-07-14

前回作成した祝日クラスを使って、サンプルとしてカレンダーを作ってみました。
飾りっ気はありませんが、テーブルタグは使わず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として保存したファイルを使います。

6
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?