参考于
视频地址

首先, 我们完成基本的 HTML 和 CSS 样式

<!doctype html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><link rel="stylesheet" href="css/calendar.css"><title>Title</title>
</head>
<body><div class="calendar"><div class="header"><button class="lastYear" title="去年"><<</button><button class="lastMonth" title="上一个月"><</button><div class="currentDate">2022-8-21</div><button class="nextMonth" title="下一个月">></button><button class="nextYear" title="明年">>></button></div><div class="days"><div class="day">星期一</div><div class="day">星期二</div><div class="day">星期三</div><div class="day">星期四</div><div class="day">星期五</div><div class="day">星期六</div><div class="day">星期天</div></div><div class="dates"><button class="date">1</button><button class="date">2</button><button class="date">3</button><button class="date">4</button><button class="date">5</button><button class="date">6</button><button class="date">7</button><button class="date">8</button><button class="date">9</button><button class="date">10</button><button class="date">11</button><button class="date">12</button><button class="date">13</button><button class="date">14</button><button class="date">15</button><button class="date">16</button><button class="date">17</button><button class="date">18</button><button class="date">19</button><button class="date">20</button><button class="date">21</button><button class="date">22</button><button class="date">23</button><button class="date">24</button><button class="date">25</button><button class="date">26</button><button class="date">27</button><button class="date">28</button><button class="date">29</button><button class="date">30</button><button class="date">31</button><button class="date">32</button><button class="date">33</button><button class="date">34</button><button class="date">35</button><button class="date">36</button><button class="date">37</button><button class="date">38</button><button class="date">39</button><button class="date">40</button><button class="date">41</button><button class="date">42</button></div></div>
</body>
<script src="js/calendar.js"></script>
</html>
:root {--accent-color: #ff2189;font-size: 2vmin;
}* {margin: 0;padding: 0;box-sizing: border-box;
}body {width: 100vm;height: 100vh;display: grid;place-items: center;font-family: Verdana, Geneva, Tahoma, sans-serif;
}.calendar {width: 90vw;user-select: none; /* 用户是否可以选择文本 */
}.calendar .header {display: flex;justify-content: space-evenly;margin-bottom: 20px;
}.calendar .header .currentDate {font-size: 2rem;font-weight: bold;color: var(--accent-color);text-align: center;
}.calendar .header button {border: 2px solid transparent;outline: none;background-color: transparent; /*消除背景颜色*/cursor: pointer;padding: 0 10px;transition: all 0.3s; /* 给hover 上颜色时给一个动画 */font-size: 1.3rem;
}.calendar .header button:hover {border: 2px solid var(--accent-color);
}.calendar .days {display: flex;justify-content: space-around;text-align: center;padding: 20px 0;font-size: 1.2rem;color: var(--accent-color);
}.calendar .dates {display: grid;grid-template-columns: repeat(7, 1fr);gap: 8px;
}.calendar .dates .date {text-align: center;padding: 20px 0;font-size: 1.6rem;transition: all 0.3s;border: 3px solid transparent;color: rgba(0, 0, 0, 0.36);cursor: pointer;background-color: transparent;box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1);
}.calendar .dates .date.selected,.calendar .dates .date:hover  {border-color: var(--accent-color);
}.calendar .dates .date.currentMonth {color: #000;
}.calendar .dates .date:hover {border-color: var(--accent-color);
}.calendar .dates .date.selected {background-color: var(--accent-color);color: #fff;
}

然后 ,编写 JS 的部分

我们分为三步分

  1. Calendar 类的初始化
  2. 编写 Calendar 类对对应元素的更新
  3. 编写对应的事件驱动

Calendar 类的初始化

我们传入两个参数, 第一个参数是管理哪个元素之下的 Calendar 组件, 第二个是初始化Calendar 的日期对象, 并且调用 init 方法完成初始化

class Calendar {// 成员属性element;defaultDate;// 当前的年月日 , 及其合并的字符串#year;#month;#date;#dateString;/*** 构造函数, 初始化数据* @param element* @param defaultDate*/constructor({element, defaultDate}) {if (element instanceof HTMLElement) {this.element = element} else {throw new Error("Calendar class property `element` not instance HTMLElement")}if (defaultDate instanceof Date) {this.defaultDate = defaultDate} else {this.defaultDate = new Date()}this.init();}/*** 初始化方法,*/init() {let fullYear = this.defaultDate.getFullYear();let month = this.defaultDate.getMonth() + 1;let date1 = this.defaultDate.getDate();// 设置日期this.setDate(fullYear, month, date1)// 添加事件this.listenEvents();}
}// 创建 calendar 类
let element = document.querySelector(".calendar");
let date = new Date();
new Calendar({element, date})

编写 Calendar 类对对应元素的更新

该方法的核心就是 setDate 方法, 也是最重要的方法

  1. 更新本类的成员属性 year mouth date dateString
  2. 更新日期的子控件
    1. 核心就是获取上个月的最后一天的星期几来判断本月份要在前面加几个上个月的子控件
    2. 如果上个月最后一天是星期四, 那么就加上 4 个上个月的子控件, 另外, 如果上个月最后一天是星期天, 那么不需要加上上个月的子控件
    3. 下一个月的子控件就是渲染完了本月的子控件后渲染剩余的部分即可(总共 42 个子控件, 对应6周)
/*** 设置日期, 并且重置当前的属性* @param year* @param mouth* @param date* @param reRenderDate*/setDate(year, mouth, date) {this.#year = year;this.#month = mouth;this.#date = date;// 设置 #dateStringlet querySelector = this.element.querySelector(".currentDate");this.#dateString = this.getCurrentDate(year, mouth, date);querySelector.innerHTML = this.#dateString// 更新当前日期的子控件this.renderDates()}/*** 获取当前日期的组合字符串 如 2022-8-21* @param year* @param mouth* @param date* @returns {string}*/getCurrentDate(year, mouth, date) {if (date) {return `${year}-${mouth}-${date}`} else {return `${year}-${mouth}`}}/*** 更新子控件*/renderDates() {// 1. 首先获取内部信息, 并且清空let dates = this.element.querySelector(".dates");dates.innerHTML = ''// 2. 获取本月和上个月和下个月的数据let currentMouthInfo = this.getMouthInfo(this.#year, this.#month)let lastMouthInfo = this.getMouthInfo(this.#year, this.#month - 1)let nextMouthInfo = this.getMouthInfo(this.#year, this.#month + 1)// 3. 向 dates 中填充数据for (let i = 1; i <= 42; i++) {let button = document.createElement('button');button.classList.add('date');let dateString;let date;if (i <= lastMouthInfo.mouthLastDay) {// 上个月let number = lastMouthInfo.mouthDateCount - lastMouthInfo.mouthLastDay + i;date = numberdateString = this.getCurrentDate(lastMouthInfo.year, lastMouthInfo.mouth, number)}else if (i > lastMouthInfo.mouthLastDay + currentMouthInfo.mouthDateCount) {// 下个月let number = i - lastMouthInfo.mouthLastDay - currentMouthInfo.mouthDateCountdate = numberdateString = this.getCurrentDate(nextMouthInfo.year, nextMouthInfo.mouth, number)}else {// 本月let number = i - lastMouthInfo.mouthLastDaydate = numberdateString = this.getCurrentDate(currentMouthInfo.year, currentMouthInfo.mouth, number)button.classList.add('currentMonth');if (date === this.#date) {// 当前选中样式button.classList.add("selected")}}// 加入 dates DOM 中button.innerHTML = datebutton.title =dateStringdates.append(button)}}/*** 获取一个月份的基本信息, 月份 年份 月份日期总数 最后一天星期几等等* @param year* @param mouth* @returns {{mouth: number, year: number, mouthLastDay: number, mouthDateCount: number}}*/getMouthInfo(year, mouth) {// mouth 数据为 0 ~ 13 , 正常为 1 ~ 12 , 0 和 13 即去年和明年if (mouth <= 0) {// 这变成了去年 12 月份mouth = 12year = year - 1} else if (mouth >= 13) {// 明年 1 月份mouth = 1year = year + 1} else {// 本年}// 获取本月的日期数量let mouthDateCount = this.getMouthDateCount(year, mouth);// 获取最后一天星期几let mouthLastDay = this.getMouthLastDay(year, mouth);return {year, mouth, mouthDateCount, mouthLastDay}}/*** 获取该月份的日期数量* @param year* @param mouth* @returns {number}*/getMouthDateCount(year, mouth) {// 当 date 为 0 时, 代表本月最后一天// 直接设置的时候是几月就是几月份return new Date(year, mouth, 0).getDate()}/*** 获取最后一个星期几 星期天对应 0 星期一到星期六对应 1 ~ 6* @param year* @param mouth* @returns {number}*/getMouthLastDay(year, mouth) {// 当 date 为 0 时, 代表本月最后一天// 直接设置的时候是几月就是几月份return new Date(year, mouth, 0).getDay()}

编写对应的事件驱动

事件驱动如下 , 核心就在于子控件的时间通过冒泡事件的方式实现

  /*** 绑定事件*/listenEvents() {// DOMSconst lastYearButton = this.element.querySelector('.lastYear');const lastMonthButton = this.element.querySelector('.lastMonth');const nextYearButton = this.element.querySelector('.nextYear');const nextMonthButton = this.element.querySelector('.nextMonth');// last yearlastYearButton.addEventListener('click', () => {this.#year--;this.setDate(this.#year, this.#month);});lastMonthButton.addEventListener('click', () => {this.#month--;if (this.#month < 1) {this.#month = 12this.#year = this.#year - 1}this.setDate(this.#year, this.#month);});nextYearButton.addEventListener('click', () => {this.#year++;this.setDate(this.#year, this.#month);});nextMonthButton.addEventListener('click', () => {this.#month++;if (this.#month > 12) {this.#month = 1this.#year = this.#year + 1}this.setDate(this.#year, this.#month);});// 给每个子控件绑定冒泡事件this.element.addEventListener('click', (e) => {// 如果为子控件, 就执行回调if (e.target.classList.contains('date')) {console.log(e.target.title);const params = e.target.title.split('-').map(str => parseInt(str, 10));// 重新更新子控件this.setDate(...params)}})}

完整代码

class Calendar {// 成员属性element;defaultDate;// 当前的年月日 , 及其合并的字符串#year;#month;#date;#dateString;/*** 构造函数, 初始化数据* @param element* @param defaultDate*/constructor({element, defaultDate}) {if (element instanceof HTMLElement) {this.element = element} else {throw new Error("Calendar class property `element` not instance HTMLElement")}if (defaultDate instanceof Date) {this.defaultDate = defaultDate} else {this.defaultDate = new Date()}this.init();}/*** 初始化方法,*/init() {let fullYear = this.defaultDate.getFullYear();let month = this.defaultDate.getMonth() + 1;let date1 = this.defaultDate.getDate();// 设置日期this.setDate(fullYear, month, date1)// 添加事件this.listenEvents();}/*** 设置日期, 并且重置当前的属性* @param year* @param mouth* @param date* @param reRenderDate*/setDate(year, mouth, date) {this.#year = year;this.#month = mouth;this.#date = date;// 设置 #dateStringlet querySelector = this.element.querySelector(".currentDate");this.#dateString = this.getCurrentDate(year, mouth, date);querySelector.innerHTML = this.#dateString// 更新当前日期的子控件this.renderDates()}/*** 获取当前日期的组合字符串 如 2022-8-21* @param year* @param mouth* @param date* @returns {string}*/getCurrentDate(year, mouth, date) {if (date) {return `${year}-${mouth}-${date}`} else {return `${year}-${mouth}`}}/*** 更新子控件*/renderDates() {// 1. 首先获取内部信息, 并且清空let dates = this.element.querySelector(".dates");dates.innerHTML = ''// 2. 获取本月和上个月和下个月的数据let currentMouthInfo = this.getMouthInfo(this.#year, this.#month)let lastMouthInfo = this.getMouthInfo(this.#year, this.#month - 1)let nextMouthInfo = this.getMouthInfo(this.#year, this.#month + 1)// 3. 向 dates 中填充数据for (let i = 1; i <= 42; i++) {let button = document.createElement('button');button.classList.add('date');let dateString;let date;if (i <= lastMouthInfo.mouthLastDay) {// 上个月let number = lastMouthInfo.mouthDateCount - lastMouthInfo.mouthLastDay + i;date = numberdateString = this.getCurrentDate(lastMouthInfo.year, lastMouthInfo.mouth, number)}else if (i > lastMouthInfo.mouthLastDay + currentMouthInfo.mouthDateCount) {// 下个月let number = i - lastMouthInfo.mouthLastDay - currentMouthInfo.mouthDateCountdate = numberdateString = this.getCurrentDate(nextMouthInfo.year, nextMouthInfo.mouth, number)}else {// 本月let number = i - lastMouthInfo.mouthLastDaydate = numberdateString = this.getCurrentDate(currentMouthInfo.year, currentMouthInfo.mouth, number)button.classList.add('currentMonth');if (date === this.#date) {// 当前选中样式button.classList.add("selected")}}// 加入 dates DOM 中button.innerHTML = datebutton.title =dateStringdates.append(button)}}/*** 获取一个月份的基本信息, 月份 年份 月份日期总数 最后一天星期几等等* @param year* @param mouth* @returns {{mouth: number, year: number, mouthLastDay: number, mouthDateCount: number}}*/getMouthInfo(year, mouth) {// mouth 数据为 0 ~ 13 , 正常为 1 ~ 12 , 0 和 13 即去年和明年if (mouth <= 0) {// 这变成了去年 12 月份mouth = 12year = year - 1} else if (mouth >= 13) {// 明年 1 月份mouth = 1year = year + 1} else {// 本年}// 获取本月的日期数量let mouthDateCount = this.getMouthDateCount(year, mouth);// 获取最后一天星期几let mouthLastDay = this.getMouthLastDay(year, mouth);return {year, mouth, mouthDateCount, mouthLastDay}}/*** 获取该月份的日期数量* @param year* @param mouth* @returns {number}*/getMouthDateCount(year, mouth) {// 当 date 为 0 时, 代表本月最后一天// 直接设置的时候是几月就是几月份return new Date(year, mouth, 0).getDate()}/*** 获取最后一个星期几 星期天对应 0 星期一到星期六对应 1 ~ 6* @param year* @param mouth* @returns {number}*/getMouthLastDay(year, mouth) {// 当 date 为 0 时, 代表本月最后一天// 直接设置的时候是几月就是几月份return new Date(year, mouth, 0).getDay()}/*** 绑定事件*/listenEvents() {// DOMSconst lastYearButton = this.element.querySelector('.lastYear');const lastMonthButton = this.element.querySelector('.lastMonth');const nextYearButton = this.element.querySelector('.nextYear');const nextMonthButton = this.element.querySelector('.nextMonth');// last yearlastYearButton.addEventListener('click', () => {this.#year--;this.setDate(this.#year, this.#month);});lastMonthButton.addEventListener('click', () => {this.#month--;if (this.#month < 1) {this.#month = 12this.#year = this.#year - 1}this.setDate(this.#year, this.#month);});nextYearButton.addEventListener('click', () => {this.#year++;this.setDate(this.#year, this.#month);});nextMonthButton.addEventListener('click', () => {this.#month++;if (this.#month > 12) {this.#month = 1this.#year = this.#year + 1}this.setDate(this.#year, this.#month);});// 给每个子控件绑定冒泡事件this.element.addEventListener('click', (e) => {// 如果为子控件, 就执行回调if (e.target.classList.contains('date')) {console.log(e.target.title);const params = e.target.title.split('-').map(str => parseInt(str, 10));// 重新更新子控件this.setDate(...params)}})}
}// 创建 calendar 类
let element = document.querySelector(".calendar");
let date = new Date();
new Calendar({element, date})

前端Demo - 日历控件纯原生实现相关推荐

  1. [前端]WdatePicker日历控件使用方法

    转载地址: http://www.cnblogs.com/yuhanzhong/archive/2011/08/10/2133276.html 1.跨无限级框架显示 无论你把日期控件放在哪里,你都不需 ...

  2. 前端常用插件之calender日历控件

    前端常用插件之calender日历控件 最近,发现一个插件--简单好用的页面日历控件,个人觉得有必要与大家分享一下,它就是calender日历控件. 准备环境: Bootstrap文件:bootstr ...

  3. java自定义日历控件_【无私分享】修订版干货!!!一个炫酷的自定义日历控件,摆脱日历时间选择烦恼,纯福利~...

    可能不少的小伙伴都有看楼主昨天发的自定义日历控件,虽然实现功能不多,但也还算将就吧. 但是看了的小伙伴就很心急了,说楼主上传到gitHub的东西有问题,楼主下载来看了看,基本都没问题吧,没弄好的小伙伴 ...

  4. 【无私分享】修订版干货!!!一个炫酷的自定义日历控件,摆脱日历时间选择烦恼,纯福利~...

    可能不少的小伙伴都有看楼主昨天发的自定义日历控件,虽然实现功能不多,但也还算将就吧. 没有看的小伙伴如果有兴趣的话可以去看看:http://www.cnblogs.com/liushilin/p/57 ...

  5. 纯前端表格开发控件SpreadJS:类Excel,功能涵盖Excel的 95% 以上

    下载最新版纯前端表格控件SpreadJS Excel 作为一款深受用户喜爱的电子表格工具,借助其直观的界面.出色的计算性能.数据分析和图表,已经成为数据统计领域不可或缺的软件之一. 基于Excel对数 ...

  6. 【无私分享】干货!!!一个炫酷的自定义日历控件,摆脱日历时间选择烦恼,纯福利~...

    最近公司项目中有一个按日期查看信息的功能,楼主本想用之前用的wheelView将就使用的,不过产品经理有个新要求,就是点击按钮弹出的日期选择对话框必须显示农历节假日,周几什么的.这可就难为人了,倘若使 ...

  7. Android开源的精美日历控件,热插拔设计的万能自定义UI,flutter调用原生sdk

    XML用法 如果需要在日历控件下方使用其它控件,使用CalendarLayout控件即可,calendar_content_view_id为其它控件的id,支持任意控件,如RecyclerView.L ...

  8. 【前端】小程序日历控件分享

    之前小程序开发需要用到日历控件,但是关于小程序可用的实在资源有限,于是就自己写了一个,如今项目上线后将它分享出来,希望能帮到需要的人. 效果图: JS代码: dateData: function () ...

  9. CalendarView使用详细文档(一个很不错的日历控件)

    github地址:https://github.com/huanghaibin-dev/CalendarView CalendarView使用详细文档 日历控件定制是移动开发平台上比较常见的而且比较难 ...

  10. 推荐一款优雅的日历控件

    原文链接:https://mp.weixin.qq.com/s/SmxDiWIidHS2hwVvFcz_hw 项目需要用到日历控件,这是我们的效果图. 去github上搜了一哈,搜到大神写的Calen ...

最新文章

  1. 生态伙伴 | 股书入驻飞书,提供一站式股权激励方案设计及管理服务
  2. mysql crm动态列设计_值得收藏:一份非常完整、详细的MySQL规范
  3. RTP协议全解析(H264码流和PS流)(转)
  4. Bash脚本教程之脚本除错
  5. linuc和python常用命令是一样的么_linuc和python常用命令是一样的么_python教程3--《linux常用命令》...
  6. 《Python CookBook2》 第四章 Python技巧 对象拷贝 通过列表推导构建列表
  7. (转)“宇宙之王”高盛在历史的交叉口,不得不全面走向机器自动化
  8. 高位在前和低位在前区别_前、后级功放、合并功放区别是什么?
  9. 软件开发基本流程概述
  10. 十四步实现强大的五子棋AI
  11. 天天生鲜项目——商品详情页
  12. 仿真软件proteus构建LCD1602四线驱动实验
  13. UGUI——RectTransform详解
  14. 刮刮卡 vue canvas
  15. Python 面向对象版学员管理系统
  16. RocketMQ消息发送源码解析
  17. 如何打造数据化决策管理
  18. 《VC++深入详解》第三章
  19. 最小相位滤波器 matlab,基于MATLAB最小相位数字滤波器的设计方法研究
  20. linux终端窗口如何切换快捷键,【linux基础】Ubuntu下的终端多标签切换快捷键

热门文章

  1. Spring代码实例系列-绪论
  2. 2021爱分析・中国采购数字化趋势报告
  3. kindle升级失败变砖(卡大树)维修步骤
  4. Excel:数据处理
  5. java常用开发工具大合集
  6. 计算机考研浙江理工和江苏大学,杭州电子科技大和浙江理工大学这两所大学怎么样?哪所好?...
  7. AMOS软件简介【SPSS 050期】
  8. UOS安装谷歌浏览器并设置中文
  9. qq令牌64位密钥提取_令牌QQ号代码64位数字+字母只截图保存可转换文字再来获取口令...
  10. 德语翻译器在线翻译中文-德语翻译器支持各大语言翻译