JavaScript 原生实现自定义日历签到功能
基于 JavaScript 和 jQuery 构建了一个轻量级的日历签到组件,支持按月份浏览历史签到记录,并通过特定的 CSS 类区分已签到、当天可签到及空白状态。
效果预览
最终实现的日历界面将清晰展示当前月份的日程分布,红色背景标识已完成的签到,橙色标识今日待签到的位置。切换月份时,组件会根据新的时间范围重新渲染日历网格。
技术架构与实现
该方案采用模块化设计,将所有日历相关的逻辑封装在 calUtil 对象中。整体结构包含初始化入口、DOM 渲染绘制、日期运算逻辑以及事件绑定四个部分。为了简化 DOM 操作与 AJAX 请求(示例中为静态模拟),引入了 jQuery 库。
第1步:引入依赖与环境配置
在 HTML 页面中引入必要的样式表和脚本资源,并确保 jQuery 版本兼容(如 1.12.4)。
<script type="text/javascript" src="jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="sign.css" /> <script type="text/javascript" src="calendar.js"></script>
第2步:定义签到数据源
签到数据通常由后端接口返回 JSON 格式。在示例中,我们使用一个包含 signDay 字段的数组来模拟已签到日期。这些数据将作为初始参数传递给日历工具。
var signList = [
{"signDay": "10"},
{"signDay": "11"},
{"signDay": "12"},
{"signDay": "13"}
];
calUtil.init(signList);第3步:日历核心逻辑封装 (calendar.js)
calendar.js 文件包含了构建日历所需的全部算法。主要功能点包括:
- 年份与月份计算:通过
getDaysInmonth方法动态获取指定月份的天数,利用switch语句处理上一月、下一月以及初次加载时的日期重置逻辑。 - 日历网格生成:使用
bulidCal方法创建一个二维数组来存储日历格子的内容,第一行固定为星期表头(日~六),后续行填充具体日期。 - 状态判断:通过
ifHasSigned遍历签到列表判断某一天是否已签到;同时结合系统当前日期,识别出“今天”。 - DOM 渲染:
drawCal方法根据生成的日历数据和状态,拼接 HTML 字符串并注入到容器中。它会为已签到日期添加on类,为今天未签到的日期添加today类并绑定点击事件。
var calUtil = {
// 当前显示的年月
showYear: new Date().getFullYear(),
showMonth: new Date().getMonth() + 1,
eventName: "load",
// 初始化
init: function(signList) {
calUtil.setMonthAndDay();
calUtil.draw(signList);
calUtil.bindEnvent();
},
// 渲染日历主体
draw: function(signList) {
var str = calUtil.drawCal(calUtil.showYear, calUtil.showMonth, signList);
$("#calendar").html(str);
$(".calendar_month_span").html(calUtil.showYear + "年" + calUtil.showMonth + "月");
},
// 绑定月份切换事件
bindEnvent: function() {
$(".calendar_month_prev").click(function() {
var signList = [{"signDay":"10"},{"signDay":"11"},{"signDay":"12"},{"signDay":"13"}];
calUtil.eventName = "prev";
calUtil.init(signList);
});
$(".calendar_month_next").click(function() {
var signList = [{"signDay":"10"},{"signDay":"11"},{"signDay":"12"},{"signDay":"13"}];
calUtil.eventName = "next";
calUtil.init(signList);
});
},
// 更新显示的时间
setMonthAndDay: function() {
switch(calUtil.eventName) {
case "load":
var current = new Date();
calUtil.showYear = current.getFullYear();
calUtil.showMonth = current.getMonth() + 1;
break;
case "prev":
var nowMonth = $(".calendar_month_span").html().split("年")[1].split("月")[0];
calUtil.showMonth = parseInt(nowMonth) - 1;
if (calUtil.showMonth == 0) {
calUtil.showMonth = 12;
calUtil.showYear -= 1;
}
break;
case "next":
var nowMonth = $(".calendar_month_span").html().split("年")[1].split("月")[0];
calUtil.showMonth = parseInt(nowMonth) + 1;
if (calUtil.showMonth == 13) {
calUtil.showMonth = 1;
calUtil.showYear += 1;
}
break;
}
},
// 获取某个月的天数
getDaysInmonth: function(iMonth, iYear) {
var dPrevDate = new Date(iYear, iMonth, 0);
return dPrevDate.getDate();
},
// 签到动作
qiandao: function() {
alert("签到成功");
var signList = [{"signDay":"10"},{"signDay":"11"},{"signDay":"12"},{"signDay":"13"},{"signDay":"17"}];
calUtil.init(signList);
},
// 构建日历数据结构
bulidCal: function(iYear, iMonth) {
var aMonth = new Array(7);
for(let i=0;i<7;i++){aMonth[i] = new Array(7);}
var dCalDate = new Date(iYear, iMonth - 1, 1);
var iDayOfFirst = dCalDate.getDay();
var iDaysInMonth = calUtil.getDaysInmonth(iMonth, iYear);
var iVarDate = 1;
// 设置表头
aMonth[0][0] = "日"; aMonth[0][1] = "一"; aMonth[0][2] = "二";
aMonth[0][3] = "三"; aMonth[0][4] = "四"; aMonth[0][5] = "五"; aMonth[0][6] = "六";
// 填充第一个月的日期
for (var d = iDayOfFirst; d < 7; d++) {
if (iVarDate <= iDaysInMonth) {
aMonth[1][d] = iVarDate;
iVarDate++;
} else {
aMonth[1][d] = null;
}
}
// 填充剩余周
for (var w = 2; w < 7; w++) {
for (var d = 0; d < 7; d++) {
if (iVarDate <= iDaysInMonth) {
aMonth[w][d] = iVarDate;
iVarDate++;
} else {
aMonth[w][d] = null;
}
}
}
return aMonth;
},
// 检查是否已签到
ifHasSigned: function(signList, day) {
var signed = false;
$.each(signList, function(index, item) {
if (item.signDay == day) {
signed = true;
return false;
}
});
return signed;
},
// 绘制 HTML
drawCal: function(iYear, iMonth, signList) {
var myMonth = calUtil.bulidCal(iYear, iMonth);
var htmls = [];
htmls.push("<div class='sign_main' id='sign_layer'>");
htmls.push("<div class='sign_succ_calendar_title'>");
htmls.push("<div class='calendar_month_next'>下月</div>");
htmls.push("<div class='calendar_month_prev'>上月</div>");
htmls.push("<div class='calendar_month_span'></div>");
htmls.push("</div>");
htmls.push("<div class='sign' id='sign_cal'>");
htmls.push("<table>");
htmls.push("<tr>");
for(var i=0;i<7;i++){
htmls.push("<th>" + myMonth[0][i] + "</th>");
}
htmls.push("</tr>");
for (var w = 1; w < 7; w++) {
htmls.push("<tr>");
for (var d = 0; d < 7; d++) {
var cellVal = myMonth[w][d];
var isNull = (cellVal === null || isNaN(cellVal));
var displayVal = (!isNull ? cellVal : " ");
var hasSigned = !isNull && calUtil.ifHasSigned(signList, cellVal);
var isToday = !isNull && cellVal == new Date().getDate()
&& new Date().getMonth() + 1 == calUtil.showMonth;
if (isToday && !hasSigned) {
// 今天是当前月,且未签到,绑定签到事件
htmls.push("<td οnclick='calUtil.qiandao()' class='today'>" + displayVal + "</td>");
} else {
if (hasSigned) {
htmls.push("<td class='on'>" + displayVal + "</td>");
} else {
htmls.push("<td>" + displayVal + "</td>");
}
}
}
htmls.push("</tr>");
}
htmls.push("</table>");
htmls.push("</div>");
htmls.push("</div>");
return htmls.join('');
}
};第4步:CSS 样式美化 (sign.css)
CSS 负责控制日历的外观,特别是不同状态下的背景色和布局对齐。关键样式如下:
/* 表格基础样式 */
.sign table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
}
.sign th, .sign td {
width: 30px;
height: 40px;
text-align: center;
line-height: 40px;
border: 1px solid #e3e3e3;
}
/* 已签到样式 - 红色背景 */
.sign .on {
background-color: red;
}
/* 今天未签到样式 - 橙色背景 */
.sign .today {
background-color: #FFAA33;
cursor: pointer;
}
/* 月份切换按钮与标题布局 */
.calendar_month_next, .calendar_month_prev {
width: 34px;
height: 40px;
cursor: pointer;
}
.calendar_month_span {
font-weight: bold;
color: #656565;
}总结
通过上述步骤,我们实现了一个具备月份切换、状态标记和基础交互功能的日历签到组件。开发者可以将 signList 替换为实际的 API 请求结果,以支持动态数据加载。以上就是 JavaScript 自定义日历签到功能的详细内容,更多关于前端日历开发、JavaScript 组件封装的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













