Table of Contents
构建自定义日历应用程序是加深您对JavaScript,浏览器文档对象模型(DOM),以及客户端数据持续性的理解的绝佳方法. 通过杠杆化的Web存储API——具体来说是本地存储——您可以创建一个完全功能化,事件驱动的日历,在浏览器中运行,而不需要后端服务器或数据库. 这个教程会将您从构建HTML到实施拖放事件管理等高级功能的每一个步骤.
为什么要构建自定义日历 ?
预建的日历库(如 FullCalendar 或 DayPilot) 提供了快速解决方案, 但从头开始构建一个程序会让您完全控制用户界面、 数据处理和性能。 您还获得了JavaScript 核心概念的手动体验, 如 [[FLT: 0] 对象、 事件授权 和 [[FLT: 1] API 。 由此生成的应用程序可以扩展为项目管理工具、 预订系统或个人日志 。
设置 HTML 结构
从一个干净的 HTML 骨架开始。日历需要三个主要组成部分:导航栏(用于变化的月份)、网格容器(用于日格)和事件输入表。使用[]语义类名称的元素进行易的造型和脚本。
<div id="calendar">
<div class="calendar-header">
<button id="prevBtn">←</button>
<h2 id="monthYear"></h2>
<button id="nextBtn">→</button>
</div>
<div class="calendar-grid"></div>
<div class="event-form">
<input type="text" id="eventInput" placeholder="Add an event..." />
<button id="saveEvent">Save</button>
</div>
</div>
将由 JavaScript 动态地进行。对于移动的“方便”布局,请考虑使用7 + 列 CSS 网格(每周每天一个) 。将第一行保留为日名(Sun-Sat )。
使用 CSS 设置日历
干净,反应灵敏的 CSS 将原始的 HTML 转换成可用的接口。使用 CSS Grid 来设置日历网格, 设置 [[FLT: 5]] 。 在月一日前添加间隔数。 对于访问, 确保充分的颜色对比, 并在交互元素上包含 [[FLT: 6] 样式。 下面显示一个最小样式表( 内置或链接) 。
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
}
.day-cell {
min-height: 80px;
border: 1px solid #ccc;
padding: 4px;
position: relative;
}
.day-cell.today {
background-color: #eef;
}
.event {
font-size: 0.8em;
background: #4caf50;
color: #fff;
padding: 2px 4px;
margin: 1px 0;
border-radius: 3px;
cursor: pointer;
}
使用媒体查询在窄屏幕上缩小单元格大小,并考虑隐藏事件文本,只显示一个计数.
核心 Java脚本: 生成日历网格
所有动态渲染都以 JavaScript 中存在。 中心函数 [[FLT: 8]] 计算月的第一天和总天数。 使用 [[FLT: 9] 来获取周日索引( 0 = 星期日) 。 然后从月的1 日到月的最后一日循环生成单元格 。
与日期对象合作
对象 [[FLT: 10]] 是日历逻辑的支柱。 要获得一个月的最后一天, 请使用 [[FLT: 11]] (下个月的第0天给出当月的最后一天) 。 将当前月份和年份存储在全局变量中, 以便导航更新它们 。
建设网格
function renderCalendar() {
const grid = document.querySelector('.calendar-grid');
grid.innerHTML = '';
const firstDay = new Date(currentYear, currentMonth, 1).getDay();
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
// Add empty cells for days before the 1st
for (let i = 0; i < firstDay; i++) {
const empty = document.createElement('div');
empty.className = 'day-cell empty';
grid.appendChild(empty);
}
// Generate day cells
for (let day = 1; day <= daysInMonth; day++) {
const cell = document.createElement('div');
cell.className = 'day-cell';
cell.dataset.date = `${currentYear}-${currentMonth + 1}-${day}`;
const dateSpan = document.createElement('span');
dateSpan.textContent = day;
cell.appendChild(dateSpan);
// Highlight today
const today = new Date();
if (currentYear === today.getFullYear() && currentMonth === today.getMonth() && day === today.getDate()) {
cell.classList.add('today');
}
// Load events for this date
loadEvents(cell);
grid.appendChild(cell);
}
}
更新标题中的月/年显示:
月份之间的导航
将单击事件听众附加到上一个和下个按钮。 增加或缩小 [[FLT: 14] ] , 必要时调整年份( 例如, 12月到下一年1月之后) 。 在更新变量后, 请再次拨打 [[FLT: 15] ] 。
document.getElementById('prevBtn').addEventListener('click', () => {
currentMonth--;
if (currentMonth < 0) {
currentMonth = 11;
currentYear--;
}
renderCalendar();
});
document.getElementById('nextBtn').addEventListener('click', () => {
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
currentYear++;
}
renderCalendar();
});
对于额外可用性,请添加一个 当今 按钮,将视图重排到当月,并突出显示当前的日子.
存储和amp; 以本地存储获取事件
本地存储 提供了一个简单的键值存储,在浏览器重启后幸存。使用一致的键态,例如],存储 JSON 阵列的事件对象。包含诸如、、和]等属性。
保存新事件
当用户点击“保存”按钮时, 抓取输入值和当前选中的日期 。 (您可以添加一个取日期器, 也可以检测单击哪一个日单元格 。 ) 从本地存储器中获取该日期的任何现有事件, 推新事件, 然后保存回 。
function saveEvent(dateKey, eventText) {
const existing = JSON.parse(localStorage.getItem(dateKey)) || [];
existing.push({ id: Date.now(), text: eventText, time: new Date().toLocaleTimeString() });
localStorage.setItem(dateKey, JSON.stringify(existing));
renderCalendar(); // refresh to show new event
}
将事件加载到日格
在 ] 内部,对于每个非空日单元格,请调用一个函数,该函数会查询本地存储该日期键。如果事件存在,请通过它们循环,并附加 元素到单元格中。
function loadEvents(cell) {
const dateKey = cell.dataset.date;
const events = JSON.parse(localStorage.getItem(dateKey));
if (!events) return;
events.forEach(evt => {
const eventDiv = document.createElement('div');
eventDiv.className = 'event';
eventDiv.textContent = evt.text;
eventDiv.dataset.id = evt.id;
// Optional: add a delete button
cell.appendChild(eventDiv);
});
}
删除和编辑事件
要删除, 请在每个事件元素( 或使用事件代表团) 中附加点击事件 。 请为该日期检索阵列, 以 [FLT: 26] 过滤出事件, 并将更新后的阵列保存到本地存储。 编辑时可以双击事件, 提示新文本, 并更新阵列 。
提高功能的高级功能
基本日历工作完毕后,可以添加显著改善用户体验的功能.
颜色 ++ 按类型分列的编码事件
允许用户指定类别( 如工作、 个人、 健康 ) 。 在本地存储中存储 a [[FLT: 27] ] 字段。 在 CSS 中, 定义类如 [[FLT: 28]] , [[FLT: 29]] , 并动态应用 。
拖曳和拖曳
使用 HTML5 拖放 API 来移动日格之间的事件。 当事件在不同的日子里被丢弃时, 更新本地存储中的日期键: 从原日期数组中删除事件, 并将其推到新日期的数组 。
经常性活动(周/月)
在保存事件时添加“ 重复” 选项。 在加载功能中, 如果当前日期符合重现规则( 例如每个星期一) , 则计算并显示相应的事件。 将重现规则存储在单独的本地存储密钥中, 以避免重现 。
周视图和小型日历
切换月与周的视图。 一周的视图只显示本周的日数, 并显示更多的横向空间。 月- 月- 月视图的小型日历可以帮助用户快速跳到任何日期 。
使日历能够响应和方便使用
测试不同屏幕大小的日历。 使用 [[ FLT: 30] ] 查询将日单元格垂直堆放在非常小的屏幕上或减少粘贴。 确保所有交互元素都是键盘“ 访问: 附加 [ [FLT: 31] ] 事件, 用于 Enter and Escape。 使用 [ [FLT: 32] 属性在按钮和活区域上进行, 用于屏幕阅读器宣布月份的更改和事件添加 。
性能和数据管理提示
- 批量存储写入 : 与每次事件操作的读/写本地存储不同, 将整个月份的事件缓存在一个 JavaScript 对象中, 并定期进行冲洗更改 。
- 有限事件大小:保持事件对象小。避免存储大字符串或二进制数据。
- Fallback for resurve Limit: 本地存储通常上限为5–10 MB. 存储接近满时警告用户.
- 大数据集使用索引数据库:如果您的日历会举办数千次活动,考虑IndexedDB[,以提高性能和查询能力.
结论
创建 JavaScript 和本地存储自定义日历应用程序是一个奖励工程, 它能提升您的前端技能。 您学会了操作 DOM, 与日期合作, 管理没有服务器的状态, 并处理用户交互。 代码库可以通过服务器同步、 云备份, 或通过 [[ FLT: 0]] Google Calendar API [ [ [FLT: 1]]] 来扩展外部日历。 启动基本网格, 添加持久性, 然后在拖动的“ ” 和“ ” 滴” 等特性上分层。 结果是一个定制日历, 其行为符合您需要 。
进一步阅读时,探索MDN上的storage API文档和Drag and Drop API[. 快乐编码!