最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 面向初学者:如何写一个日历组件

    正文概述 掘金(打杂小厮)   2020-12-27   511

    面向初学者:如何写一个日历组件

    这是苹果上的日历截图,从上面的内容来看,有公历,农历,24节气和节日。

    遗憾的是目前还未找到 公历农历 转换的公式;现阶段能够找到的开源的日历组件,基本上都是存了一份 农历 1900-2100 的润大小信息表,有兴趣的可以阅读下 农历编算法则 。

    而节日相对而言会受到地区等因素影响,需要定时维护,所以实际上,我们在日历上真正能够计算并使用的只有 公历 和 24节气。

    写日历需要解决的问题

    1. 每周从什么时候开始?

    这是因为不同的地区,每周开始的时间是不一样的,可以参考知乎的 一周的第一天是周一还是周日?

    平时在使用实体日历的时候,的确是有些印刷的日历会从 星期一 开始,而使用电子产品的时候,大多数都是从 星期天 开始的。

    不过这个问题还是比较好解决的,首先看下日历的头部。

    面向初学者:如何写一个日历组件 面向初学者:如何写一个日历组件

    从截图上看出,不管是那天开始,整体的顺序不变,而且有没有发现,这个情况和无缝滚动的组件很像,内容滚出视图外,自动补充到末尾,就像一个圆环。

    面向初学者:如何写一个日历组件

    那我们基于无缝滚动的原理,就很容易获得一周7天的数字。

    面向初学者:如何写一个日历组件

    既然原理分析出了,那我们就可以很容易的写出代码:

    // 在 js 中,0 是周日,0 ~ 6 代表周一至周日
    const WEEKDAYS = [0, 1, 2, 3, 4, 5, 6]
    
    // 一周只有7天,不会有第8天,所以只要拷贝一次就可以了
    const DOUBLE_WEEKDAYS = WEEKDAYS.concat(WEEKDAYS)
    
    /**
     * 获取获得一周7天的数字
     *
     * @param firstWeekDay 周开始时间
     *
     * @return 周数组
     */
    function getWeekdays(firstWeekDay = 0) {
      if (firstWeekDay === 0) return WEEKDAYS
      return DOUBLE_WEEKDAYS.slice(firstWeekDay, firstWeekDay + 7)
    }
    

    我们执行之后,基本上达到我们的预期。

    面向初学者:如何写一个日历组件

    那我们再进一步,转化成日历的头部,顺便支持国际化

    // 简体中文
    const zh = {
      // 完整名称 
      weekdays: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
      // 短名称
      weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
      // 缩写
      weekdaysAbbr: ['日', '一', '二', '三', '四', '五', '六']
    }
    
    // 英文
    const en = {
      // 完整名称 
      weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
      // 短名称
      weekdaysShort: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
      // 缩写
      weekdaysAbbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
    }
    
    /**
     * 获取一星期的名称列表
     *
     * @param {Number[]}  weekdays 一周7天的数字
     *
     * @param {Object[]}
     */
    function getWeekHead(weekdays) {
      return weekdays.map(day => ({
        name: locale.weekdays[day],
        short: locale.weekdaysShort[day],
        abbr: locale.weekdaysAbbr[day],
        day: day
      }))
    }
    

    打开 Chrome devtools 执行下,基本额可以达到我们的预期

    面向初学者:如何写一个日历组件

    2. 每个月的第一天不是周的开始

    这个很正常,周的开始都不确定,何况是月天数不固定。

    面向初学者:如何写一个日历组件

    我们从上面的图也可以看出来,月结束和开始都不固定,那我们该如何处理?

    其实这个也非常容易解决,我们先不从程序的角度思考,单纯从图片上思考下,是不是只要知道每个月的1号在处在星期中的那一天,在往前减去多少天,就可以计算出 1号所在星期 开始的时间。

    可能又点绕,我们用图片说话:

    面向初学者:如何写一个日历组件

    那我们知道如何计算之后,就可以开始准备数据了。

    首先我们需要知道,1号是星期几,这个在 js 中,直接通过 Date#getDay() 就可以知道是星期几

    面向初学者:如何写一个日历组件

    但因为前面说过了,周的开始是不固定的,所以我们计算的时候,还需要前面的函数辅助才能知道应该减少几天。

    面向初学者:如何写一个日历组件 面向初学者:如何写一个日历组件

    到这里可能会有人疑惑,每个月份的天数都不固定,而且还有润月的问题;就算我们知道要减少几天,计算上一个月的最后一天也是一个麻烦事情,这个应该如何解决呢?

    其实这个也非常简单,在 js 中 Date 对象已经帮帮我们处理好了。

    面向初学者:如何写一个日历组件

    到这里我们所有的条件都已经准备完毕了,剩下的就交给循环了。

    
    /**
     * 获取月份日历
     *
     * @param {*} year  年
     * @param {*} month 月
     * @param {Object} options
     * @param {Number[]} options.weekdays     一周7天的数组
     * @param {Number} [options.firstWeekDay=0]      周开始时间
     * @param {Number} [options.visibleWeeksCount=6] 单个日历上显示的周数量
     */
    function getMonthCalendar(year, month, options) {
      const weekdays = options.weekdays
      const cursor = new Date(year, month - 1, 1, 0, 0, 0, 0)
    
      const count = (options.visibleWeeksCount || 6) * 7
    
      // 让时间定位在周开始的时间
      cursor.setDate(cursor.getDate() - weekdays.indexOf(cursor.getDay()))
    
      const calendar = []
    
      let week = []
      for (let i = 0; i < count; i++) {
        if (!(i % 7)) {
          week = calendar[i / 7] = []
        }
    
        week.push(
          // 拷贝时间
          new Date(cursor)
        )
    
        cursor.setDate(cursor.getDate() + 1)
      }
    
      return calendar
    }
    
    function format(d) {
      return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, 0)}-${d.getDate().toString().padStart(2, 0)}`
    }
    

    我们看下执行结果

    面向初学者:如何写一个日历组件

    面向初学者:如何写一个日历组件

    面向初学者:如何写一个日历组件

    试试周一开始

    面向初学者:如何写一个日历组件

    目前看来,日期和星期都按我们预想的那样执行了,剩下的就是渲染问题。

    渲染日历

    其实当我们数据准备好的那一刻,基本上这个日历也就差不多完成了,剩下的是往上面堆功能。

    我们只要数据处理好,那我们就可以使用任何框架,甚至在任何支持 Date 对象的 js 环境中运行了,比如在 nodejs 上运行。

    所有代码都在这里:完整代码

    nodejs >= 12.x 试试:

    
    /**
     * 打印日历
     */
    function printCalendar(year, month, firstWeekday) {
      const weekdays = getWeekdays(firstWeekday)
      const calendar = getMonthCalendar(year, month, { weekdays })
      const columns = getWeekHead(weekdays, zh).map((w) => w.abbr)
    
      const rows = calendar.map((dates) => {
        const row = {}
        columns.forEach((weekday, index) => {
          const date = dates[index]
          if (isCurrentMonth(date)) {
            row[weekday] = date.getDate()
          }
        })
        return row
      })
    
      function isCurrentMonth(d) {
        return d.getFullYear() === year && d.getMonth() + 1 === month
      }
    
      const time = `  ${year}-${month.toString().padStart(2, '0')}`
      console.log('')
      console.group(time)
      console.table(rows, columns)
      console.groupEnd(time)
    }
    

    打印 2021-01 月,以周日开始的日历

    printCalendar(2021, 1, 0)
    

    面向初学者:如何写一个日历组件

    打印 2021-01 月,以周一开始的日历

    printCalendar(2021, 1, 1)
    

    面向初学者:如何写一个日历组件

    渲染到 html 上

    面向初学者:如何写一个日历组件

    结束

    农历和节假日可以通过数据的方式手动维护,不过节气可以维护到组件中。

    写一个日历,也是一件比较麻烦的事情,幸好大部分前端用到的日历都不要求 农历24节气节日,不然头发都要多掉好几根。

    如果自己维护日历数据的模块,可以试试我发布的包:@zhengxs/calendar-data,欢迎 fork


    起源地下载网 » 面向初学者:如何写一个日历组件

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元