HTML
<!DOCTYPE html>
<html>
<head>
<title>Vanilla JS Calendar</title>
<meta charset="UTF-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link href="vanillaCalendar.css" rel="stylesheet">
<style>
html {
box-sizing: border-box;
font-size: 10px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: #333;
font-size: 1.6rem;
background-color: #FAFAFA;
-webkit-font-smoothing: antialiased;
}
.logo {
margin: 1.6rem auto;
text-align: center;
}
a,
a:visited {
color: #0A9297;
}
footer {
text-align: center;
margin: 1.6rem 0;
}
h1 {
text-align: center;
}
.container {
width: 96%;
margin: 1.6rem auto;
max-width: 42rem;
text-align: center;
}
.demo-picked {
font-size: 1.2rem;
text-align: center;
}
.demo-picked span {
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>Vanilla JS Calendar</h1>
<div id="v-cal">
<div class="vcal-header">
<button class="vcal-btn" data-calendar-toggle="previous">◀
</button>
<div class="vcal-header__label" data-calendar-label="month">
2022年5月
</div>
<button class="vcal-btn" data-calendar-toggle="next">▶
</button>
</div>
<div class="vcal-week">
<span>一</span>
<span>二</span>
<span>三</span>
<span>四</span>
<span>五</span>
<span>六</span>
<span>日</span>
</div>
<div class="vcal-body" data-calendar-area="month"></div>
</div>
<p class="demo-picked">
选择日期:
<span data-calendar-label="picked"></span>
</p>
</div>
<script src="vanillaCalendar.js" type="text/javascript"></script>
<script>
window.addEventListener('load', function () {
vanillaCalendar.init({
disablePastDays: true
});
})
</script>
</body>
</html>
:root {
--vcal-bg-color: #fff;
--vcal-border-radius: 0;
--vcal-border-color: #e7e9ed;
--vcal-today-bg-color: #10989E;
--vcal-today-color: #fff;
--vcal-selected-bg-color: #E7E9ED;
--vcal-selected-color: #333;
}
#v-cal *, #v-cal *:before, #v-cal *:after {
box-sizing: border-box;
}
#v-cal {
background-color: var(--vcal-bg-color);
border-radius: var(--vcal-border-radius);
border: solid 1px var(--vcal-border-color);
box-shadow: 0 4px 22px 0 rgba(0, 0, 0, 0.05);
margin: 0 auto;
overflow: hidden;
width: 100%;
}
#v-cal .vcal-btn {
-moz-user-select: none;
-ms-user-select: none;
-webkit-appearance: button;
background: none;
border: 0;
color: inherit;
cursor: pointer;
font: inherit;
line-height: normal;
min-width: 27px;
outline: none;
overflow: visible;
padding: 0;
text-align: center;
&:active {
border-radius: var(--vcal-border-radius);
box-shadow: 0 0 0 2px rgba(var(--vcal-today-bg-color), 0.1)
}
}
#v-cal .vcal-header {
align-items: center;
display: flex;
padding: 1.2rem 1.4rem;
}
#v-cal .vcal-header svg {
fill: var(--vcal-today-bg-color);
}
#v-cal .vcal-header__label {
font-weight: bold;
text-align: center;
width: 100%;
}
#v-cal .vcal-week {
background-color: var(--vcal-selected-bg-color);
display: flex;
flex-wrap: wrap;
}
#v-cal .vcal-week span {
flex-direction: column;
flex: 0 0 14.28%;
font-size: 1.2rem;
font-weight: bold;
max-width: 14.28%;
padding: 1.2rem 1.4rem;
text-align: center;
text-transform: uppercase;
}
#v-cal .vcal-body {
background-color: rgba(var(--vcal-selected-bg-color), 0.3);
display: flex;
flex-wrap: wrap;
}
#v-cal .vcal-date {
align-items: center;
background-color: #fff;
border-radius: var(--vcal-border-radius);
display: flex;
flex-direction: column;
flex: 0 0 14.28%;
max-width: 14.28%;
padding: 1.2rem 0;
}
#v-cal .vcal-date--active {
cursor: pointer;
}
#v-cal .vcal-date--today {
background-color: var(--vcal-today-bg-color);
color: var(--vcal-today-color);
}
#v-cal .vcal-date--selected {
background-color: var(--vcal-selected-bg-color);
color: var(--vcal-selected-color);
}
#v-cal .vcal-date--disabled {
border-radius: 0;
cursor: not-allowed;
opacity: 0.5;
}
var vanillaCalendar = {
month: document.querySelectorAll('[data-calendar-area="month"]')[0],
next: document.querySelectorAll('[data-calendar-toggle="next"]')[0],
previous: document.querySelectorAll('[data-calendar-toggle="previous"]')[0],
label: document.querySelectorAll('[data-calendar-label="month"]')[0],
activeDates: null,
date: new Date(),
todaysDate: new Date(),
init: function (options) {
this.options = options
this.date.setDate(1)
this.createMonth()
this.createListeners()
},
createListeners: function () {
var _this = this
this.next.addEventListener('click', function () {
_this.clearCalendar()
var nextMonth = _this.date.getMonth() + 1
_this.date.setMonth(nextMonth)
_this.createMonth()
})
// Clears the calendar and shows the previous month
this.previous.addEventListener('click', function () {
_this.clearCalendar()
var prevMonth = _this.date.getMonth() - 1
_this.date.setMonth(prevMonth)
_this.createMonth()
})
},
createDay: function (num, day, year) {
var newDay = document.createElement('div')
var dateEl = document.createElement('span')
dateEl.innerHTML = num
newDay.className = 'vcal-date'
newDay.setAttribute('data-calendar-date', this.date)
// if it's the first day of the month
if (num === 1) {
if (day === 0) {
newDay.style.marginLeft = (6 * 14.28) + '%'
} else {
newDay.style.marginLeft = ((day - 1) * 14.28) + '%'
}
}
if (this.options.disablePastDays && this.date.getTime() <= this.todaysDate.getTime() - 1) {
newDay.classList.add('vcal-date--disabled')
} else {
newDay.classList.add('vcal-date--active')
newDay.setAttribute('data-calendar-status', 'active')
}
if (this.date.toString() === this.todaysDate.toString()) {
newDay.classList.add('vcal-date--today')
}
newDay.appendChild(dateEl)
this.month.appendChild(newDay)
},
dateClicked: function () {
var _this = this
this.activeDates = document.querySelectorAll(
'[data-calendar-status="active"]'
)
for (var i = 0; i < this.activeDates.length; i++) {
this.activeDates[i].addEventListener('click', function (event) {
var picked = document.querySelectorAll(
'[data-calendar-label="picked"]'
)[0]
picked.innerHTML = new Date(this.dataset.calendarDate).toLocaleString("zh-Hans-CN", {
weekday: "long",
hour12: false,
year: "numeric",
month: "2-digit",
day: "2-digit",
});
_this.removeActiveClass()
this.classList.add('vcal-date--selected')
})
}
},
createMonth: function () {
var currentMonth = this.date.getMonth()
while (this.date.getMonth() === currentMonth) {
this.createDay(
this.date.getDate(),
this.date.getDay(),
this.date.getFullYear()
)
this.date.setDate(this.date.getDate() + 1)
}
// while loop trips over and day is at 30/31, bring it back
this.date.setDate(1)
this.date.setMonth(this.date.getMonth() - 1)
this.label.innerHTML =
this.date.getFullYear() + '年' + this.monthsAsString(this.date.getMonth())
this.dateClicked()
},
monthsAsString: function (monthIndex) {
return [
'1月',
'2月',
'3月',
'4月',
'5月',
'6月',
'7月',
'8月',
'9月',
'10月',
'11月',
'12月'
][monthIndex]
},
clearCalendar: function () {
vanillaCalendar.month.innerHTML = ''
},
removeActiveClass: function () {
for (var i = 0; i < this.activeDates.length; i++) {
this.activeDates[i].classList.remove('vcal-date--selected')
}
}
}