Structure Overview
The layout centers a calendar grid within a fixed-width frame. A header displays the current month and year between navgiation controls. Below, a table lists weekday labels followed by date cells populated dynamically.
HTML Layout
<body>
<div class="calendar-wrapper">
<div class="calendar-container">
| <div class="header"> <span class="nav prev"><</span> <span class="month-label"></span> <span class="nav next">></span> </div> |
|---|
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
</div>
</div>
</body>
Styling
.calendar-wrapper {
width: 440px;
margin: 50px auto;
position: relative;
}
.calendar-container {
width: 420px;
margin: 10px;
position: absolute;
}
.header {
height: 60px;
text-align: center;
line-height: 60px;
position: relative;
}
.month-label {
font-size: 30px;
}
.today {
color: red;
font-weight: bold;
}
table {
background-color: #f7f7f7;
}
.weekday-row td {
font-size: 15px;
}
td {
width: 60px;
height: 60px;
text-align: center;
font-family: Simsun;
font-size: 20px;
}
.nav {
width: 60px;
height: 60px;
position: absolute;
top: 0;
}
.nav.prev { left: 0; }
.nav.next { right: 0; }
.nav:hover {
background-color: rgba(30, 30, 30, 0.2);
}
Core Logic
Two utility functions handle date calculations:
Leap Year Check
function checkLeap(y) {
return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
}
Weekday Determination
const monthLengths = [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
function getWeekday(y, m, d = 1) {
let total = (y - 1) * 365 + Math.floor((y - 1) / 4) - Math.floor((y - 1) / 100) + Math.floor((y - 1) / 400) + d;
for (let idx = 0; idx < m - 1; idx++) {
total += monthLengths[idx];
}
if (m > 2) {
total += checkLeap(y) ? 29 : 28;
}
return total % 7; // 0 = Sunday, 1 = Monday, …
}
Rendering the Grid
function renderCalendar(y, m, startIdx) {
const today = new Date();
const isCurrent = y === today.getFullYear() && m === today.getMonth() + 1;
let dim = 31;
if (m === 2) {
dim = checkLeap(y) ? 29 : 28;
} else {
dim = monthLengths[m - 1];
}
const monthLabel = document.querySelector('.month-label');
monthLabel.textContent = `${y}年${m}月`;
const grid = document.querySelector('.dates-grid');
let html = '<tr>';
for (let empty = 0; empty < startIdx; empty++) {
html += '<td></td>';
}
let colCount = startIdx;
for (let dayNum = 1; dayNum <= dim; dayNum++) {
const cellCls = (isCurrent && dayNum === today.getDate()) ? 'today' : '';
html += `<td class="${cellCls}">${dayNum}</td>`;
colCount = (colCount + 1) % 7;
if (colCount === 0 && dayNum < dim) html += '</tr><tr>';
}
while (colCount > 0 && colCount < 7) {
html += '<td></td>';
colCount++;
}
html += '</tr>';
grid.innerHTML = html;
}
Initial Display
const present = new Date();
const yr = present.getFullYear();
const mo = present.getMonth() + 1;
renderCalendar(yr, mo, getWeekday(yr, mo));
Navigation Contorls
function shiftMonth(offset) {
const label = document.querySelector('.month-label').textContent;
const nums = [...label.matchAll(/\d+/g)].map(n => parseInt(n[0]));
let [yr, mo] = [nums[0], nums[1] + offset];
if (mo > 12) { mo = 1; yr++; }
if (mo < 1) { mo = 12; yr--; }
document.querySelector('.month-label').textContent = '';
renderCalendar(yr, mo, getWeekday(yr, mo));
}
document.querySelector('.nav.next').addEventListener('click', () => shiftMonth(1));
document.querySelector('.nav.prev').addEventListener('click', () => shiftMonth(-1));