本篇文章给大家带来了关于前端日历的相关知识,其中主要跟大家聊一聊如何封装一个自定义日历组件,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。
前言
众所周知啊,一般来说,如果项目中有需要用到日历组件,往往是找第三方UI库中的组件来使用,或者是找现成的其他第三方插件。对于很多小伙伴来说,第一眼看到日历组件,下意识的就会觉得很复杂,无从下手。但是当我阅读了这个日历插件的源码之后,发现并没有我想象中的复杂。我以前傻傻得认为,想要做一个日历组件,得需要把距离现在年份前后至少十年的日历数据都获取到,然后才能进行下一步的开发。
然而,在我尝试着阅读了dycalendar.js这个库的源码之后,一方面感觉自己太笨了,把问题想得太复杂了。另外也感慨作者思路之清晰。看完之后感觉受益匪浅。
在将作者的思路逻辑梳理完毕后,我依据这个思路开发了一个vue组件。如下图所示:
接下来,就随着我一起看看如何开发一个自己的日历组件吧。
核心代码实现
1、梳理思路
- 获取到目标日期数据
- 获取到当前日期的各项重要属性,诸如
当前年
,当前月
,当前日期
,当前星期几
,当前月一共有几天
,当前月的第一天对应的是星期几
,上个月总共有多少天
等。 - 根据这些属性,来生成具体的
日历日期数据列表
,然后将其循环渲染到模板中。 - 当切换月份的时候,获取到新的目标日期对应的各项关键数据。vue检测到日历属性变化之后,通知页面进行更新。
2、初始化所需要的数据
一般来说,成熟的日历组件,日期都是一个双向绑定的变量。为了方便使用,我们也采用双向绑定的方式。
<script setup> import { reactive, ref, computed, watch } from "vue"; const props = defineProps({ modelValue: Date, }); const emits = defineEmits(["update:modelValue"]); /** * 最小年份 */ const MIN_YEAR = 1900; /** * 最大年份 */ const MAX_YEAR = 9999; /** * 目标日期 */ const targetDate = ref(props.modelValue);
接下来,我们还需要初始化一些常量用来表示月份和日期:
/** * 有关月度的名称列表 */ const monthNameList = { chineseFullName: [ "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", ], fullName: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ], mmm: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ], }; /** * 有关周几的名称列表 */ const dayNameList = [ { chineseFullName: "周日", chineseShortName: "日", fullName: "Sunday", shortName: "Sun", dayNumber: 0, }, { chineseFullName: "周一", chineseShortName: "一", fullName: "Monday", shortName: "Mon", dayNumber: 1, }, { chineseFullName: "周二", chineseShortName: "二", fullName: "Tuesday", shortName: "Tue", dayNumber: 2, }, { chineseFullName: "周三", chineseShortName: "三", fullName: "Wednesday", shortName: "Wed", dayNumber: 3, }, { chineseFullName: "周四", chineseShortName: "四", fullName: "Thursday", shortName: "Thu", dayNumber: 4, }, { chineseFullName: "周五", chineseShortName: "五", fullName: "Friday", shortName: "Fri", dayNumber: 5, }, { chineseFullName: "周六", chineseShortName: "六", fullName: "Saturday", shortName: "Sat", dayNumber: 6, }, ];
接下来,准备几个vue的响应式数据:
/** * 今日 */ const today = new Date(); /** * 日历的各项属性 */ const calendarProps = reactive({ target: { year: null, month: null, date: null, day: null, monthShortName: null, monthFullName: null, monthChineseFullName: null, firstDay: null, firstDayIndex: null, totalDays: null, }, previous: { totalDays: null, }, }); /** * 用于展现的日历数据 */ const calendarData = ref([]);
3、初始化日历的各项属性
接下来,通过setCalendarProps
方法获取日历的各个属性,逐个填充calendarProps
中的数据:
function setCalendarProps() { if (!targetDate.value) { targetDate.value = today; } // 获取目标日期的年月日星期几数据 calendarProps.target.year = targetDate.value.getFullYear(); calendarProps.target.month = targetDate.value.getMonth(); calendarProps.target.date = targetDate.value.getDate(); calendarProps.target.day = targetDate.value.getDay(); if ( calendarProps.target.year < MIN_YEAR || calendarProps.target.year > MAX_YEAR ) { console.error("无效的年份,请检查传入的数据是否是正常"); return; } // 获取到目标日期的月份【中文】名称 let dateString; dateString = targetDate.value.toString().split(" "); calendarProps.target.monthShortName = dateString[1]; calendarProps.target.monthFullName = monthNameList.fullName[calendarProps.target.month]; calendarProps.target.monthChineseFullName = monthNameList.chineseFullName[calendarProps.target.month]; // 获取目标月份的第一天是星期几,和在星期几中的索引值 const targetMonthFirstDay = new Date( calendarProps.target.year, calendarProps.target.month, 1 ); calendarProps.target.firstDay = targetMonthFirstDay.getDay(); calendarProps.target.firstDayIndex = dayNameList.findIndex( (day) => day.dayNumber === calendarProps.target.firstDay ); // 获取目标月份总共多少天 const targetMonthLastDay = new Date( calendarProps.target.year, calendarProps.target.month + 1, 0 ); calendarProps.target.totalDays = targetMonthLastDay.getDate(); // 获取目标月份的上个月总共多少天 const previousMonth = new Date( calendarProps.target.year, calendarProps.target.month, 0 ); calendarProps.previous.totalDays = previousMonth.getDate(); }
需要注意的一个知识点是,在获取本月多少天和上个月多少天的时候,都将date值设置为了
0
。这是因为当date值为0
的时候,返回的Date对象是上个月的最后一天。所以说,为了获取本月多少天,需要将本月的month值加1
。
执行这个方法之后,此时calendarProps
的值为:
4、根据日历属性生成日历日期的数据
当我们已经知道本月第一天对应的周几索引值、本月一共有多少天和上个月一共有多少天这三个核心数据之后,就可以开始生成对应的日历数据了。
思路如下:
- 由于大部分情况下,本月的第一天不是从头开始的,之前的部分是上个月的日期。所以第一行要单独进行处理。
- 设置一个公用的date数值,初始值设置为
1
。然后从本月第一天对应的周几索引值开始进行递增。本月之前的日期和之后的日期设置一个算法进行计算。 - 为了方便之后进行日期切换、样式区分,将生成的数据加工成一个对象,其中包含日期类型——
dateType
,表示是本月还是上月还是下月;
/** * 生成日历的数据 */ function setCalendarData() { let i; let date = 1; const originData = []; const firstRow = []; // 设置第一行数据 for (i = 0; i <= 6; i++) { // 设置目标月份之前月份的日期数据 if (i < calendarProps.target.firstDayIndex) { const previousDate = calendarProps.previous.totalDays - calendarProps.target.firstDayIndex + (i + 1); firstRow.push({ dateObj: new Date( calendarProps.target.year, calendarProps.target.month - 1, previousDate ), dateNumber: previousDate, dateType: "previous" }); } else { // 设置目标月份当月的日期数据 firstRow.push({ dateObj: new Date( calendarProps.target.year, calendarProps.target.month, date ), dateNumber: date, dateType: "current" }); date++; } } originData.push(firstRow); // 设置后面五行的数据 for (let j = 0; j <= 4; j++) { const rowData = []; for (let k = 0; k <= 6; k++) { // 设置目标月份剩下的日期数据 if (date <= calendarProps.target.totalDays) { rowData.push({ dateObj: new Date( calendarProps.target.year, calendarProps.target.month, date ), dateNumber: date, dateType: "current" }); } else { // 设置目标月份下个月的日期数据 const nextDate = date - calendarProps.target.totalDays; rowData.push({ dateObj: new Date( calendarProps.target.year, calendarProps.target.month + 1, nextDate ), dateNumber: nextDate, dateType: "next" }); } date++; } originData.push(rowData); } calendarData.value = originData; }
至此,这个日历组件的核心部分的逻辑就已经实现了。你看,是不是很简单?
接下来,我们只需要根据calendarData
中的数据渲染出相应的html模板和添加上样式就可以了。
5、添加模板和样式部分
一般来说,日历组件都是网格状的结构,所以我选择table的方式进行渲染。不过你要是问我还有没有别的方式,那还是有的,比如使用flex布局或者grid布局,但是如果采用这种方式的话,calendarData
的数据结构就不是现在这个样子了。
dom结构如下图:
至于按钮边框的流动效果,是我参照苏苏的文章做的,详情请见:
Clip-path实现按钮流动边框动画 juejin.cn/post/719877…
然后剩下的样式部分,即兴发挥或者根据UI设计图绘制即可。想必各位都领教过UI姐姐们精美的设计图吧(嘻嘻
具体的代码部分就不贴在文章中了,如有需要可以直接查看下方的完整源码
结语
有些感觉很麻烦的组件,可能核心逻辑往往不是那么复杂。有些时候,可能仅仅是需要一些耐心,将代码一行一行的拆解出来阅读,理清楚其中的思路。
推荐学习:《vue视频教程》
以上是深析如何封装一个vue自定义日历组件的详细内容。更多信息请关注PHP中文网其他相关文章!

VueUse 是 Anthony Fu 的一个开源项目,它为 Vue 开发人员提供了大量适用于 Vue 2 和 Vue 3 的基本 Composition API 实用程序函数。本篇文章就来给大家分享几个我常用的几个 VueUse 最佳组合,希望对大家有所帮助!

Vue3如何更好地使用qrcodejs生成二维码并添加文字描述?下面本篇文章给大家介绍一下Vue3+qrcodejs生成二维码并添加文字描述,希望对大家有所帮助。

本篇文章给大家整理分享8个GitHub上很棒的的 Vue 项目,都是非常棒的项目,希望当中有您想要收藏的那一个。

如何覆盖组件库样式?下面本篇文章给大家介绍一下React和Vue项目中优雅地覆盖组件库样式的方法,希望对大家有所帮助!

本篇文章带大家聊聊vue指令中的修饰符,对比一下vue中的指令修饰符和dom事件中的event对象,介绍一下常用的事件修饰符,希望对大家有所帮助!

如何使用VueRouter4.x?下面本篇文章就来给大家分享快速上手教程,介绍一下10分钟快速上手VueRouter4.x的方法,希望对大家有所帮助!


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

Atom编辑器mac版下载
最流行的的开源编辑器

Dreamweaver Mac版
视觉化网页开发工具

PhpStorm Mac 版本
最新(2018.2.1 )专业的PHP集成开发工具

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。