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