JavaScript 原生实现自定义日历签到功能

键盘旁的猫
键盘旁的猫
发布时间:2026-10-15 | 4人浏览过
原创

基于 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 组件封装的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表