专栏名称: 依韵宵音
前端工程师
目录
相关文章推荐
科普中国  ·  有毒但有用!广东丹霞山发现全球新物种 ·  22 小时前  
科学网  ·  用AI赋能科研工作,这些问题值得关注 ·  2 天前  
知识分子  ·  美国顶尖大学普林斯顿降薪、冻结招聘 ·  3 天前  
51好读  ›  专栏  ›  依韵宵音

带你开发一个日历控件

依韵宵音  · 掘金  ·  · 2017-12-12 02:17

正文

首发我的博客 - https://blog.cdswyda.com/post/2017121010

日历控件多的不胜枚举,为什么我们还要再造一个轮子呢?

因为大多数日历控件都是用于选择日期的,有种需求是要在日历上展示各种各样的内容,这样的日历控件较少,而且试用下来并不满意。

因此就再造一个轮子,现在带你一起基于使用之前完成的组件机制来开发一个日历控件。

需求

简单把需求整理如下:

  • 月视图
  • 支持在日历中每一天中插入任意的内容
  • 相关点击事件
  • 获取日历当前视图的开始和结束日期
  • 获取设置选中的日期

实现分析

首先我们拿系统中自带的日历观察一下,看看日历的特征到底是怎么样的。

一个月中有 28 到 31 天不等,但是为了保证完整的结构,日历中会有部分上一月和下一月的日期,总结下来,一个月中显示的必定是整整6周的日期。

那么只要得到当月的开始日期就可以绘制日历了。

如何计算当月日历视图中的开始日期呢? 前面已经分析了,为了保证完整,它显示了上一月的部分天数,那么只用从当月的1号开始往前推算就可以了。

开始日期 = 当月1号的日期 - 当月1号的星期
结束日期 = 开始日期 + 42天

这个问题搞清楚了,感觉实现这么一个日历就没什么大阻碍了,开始动工吧!

必要结构准备

首先构建如下所示的基本结构

其中:

  • 头部左右为个性化区域,用于实际使用时放置任意内容。中间用于显示当前月份和切换按钮
  • 主体区域中用绘制整个日历
    • thead 中绘制周一至周日 或周日至周一的星期,这段内容是不会随月份切换而改变的,可以直接准备好
    • tbody 中用于绘制可变的日期,准备好容器留空即可。
  • 脚部区域用于实际使用时放置任意各项化内容
  • menu区域用于切换日期时弹出的面板

绘制日历

在初始化好日历结构后就可以开始绘制日历了。

计算一个月中的开始日期和结束日期

首先完成开始和结束时间的计算

{
    // 初始化当前月份的开始日期和结束日期
    _initStartEnd: function () {
        // 当月1号
        var currMonth = moment(this.currMonth, 'YYYY-MM'),
            // 当月1号是周几 the ISO day of the week with 1 being Monday and 7 being Sunday.
            firstDay_weekday = currMonth.isoWeekday(),
            startDateOfMonth,
            endDateOfMonth;
        if (!this.dayStartFromSunday) {
            // 开始为周一 则向前减少周几的天数-1即为 开始的日期
            startDateOfMonth = currMonth.subtract(firstDay_weekday - 1, 'day');
        } else {
            // 开始为周日 则直接向前周几的天数即可
            startDateOfMonth = currMonth.subtract(firstDay_weekday, 'day');
        }

        endDateOfMonth = startDateOfMonth.clone().add(41, 'day');

        this.startDateOfMonth = startDateOfMonth;
        this.endDateOfMonth = endDateOfMonth;
    }
}

由于要处理很多日期,而JavaScript中关于日期处理时,不同浏览器下差异较大,因此直接使用 moment.js 来对日期进行统一处理。

由于使用习惯不同,一周的开始到底是周一还是周日是不确定的,因此直接作为配置即可。

绘制一月中的日期

上面已经计算得到了一个月的开始日期和结束日期,那么只用遍历进行绘制即可。

由于我们使用了表格实现,因此需要按行绘制。

实现如下:

{
    // 日历可变部分的渲染
    _render: function () {
        this._initStartEnd();

        var weeks = 6,
            days = 7,
            curDate = this.startDateOfMonth.clone(),
            tr;

        var start = this.startDateOfMonth.format('YYYY-MM-DD'),
            end = this.endDateOfMonth.format('YYYY-MM-DD');

        // 清空 并开始新的渲染
        this._clearDays();
        this._renderTitle();

        for (var i = 0; i < weeks; ++i) {
            tr = document.createElement('tr');
            tr.className = 'ep-calendar-week';
            this._daysBody.appendChild(tr);

            for (var j = 0; j < days; ++j) {
                // 渲染一天 并递增
                this._renderDay(curDate, tr);
                curDate.add(1, 'day');
            }
        }
    },
    // 每天的渲染
    _renderDay: function (date, currTr) {
        var td = document.createElement('td'),
            tdInner = document.createElement('div'),
            text = document.createElement('span'),
            day = date.isoWeekday(),
            // 返回的月份是0-11
            month = date.month() + 1;

        tdInner.appendChild(text);
        td.appendChild(tdInner);

        td.className = 'ep-calendar-date';
        tdInner.className = 'ep-calendar-date-inner';
        // 完整日期
        td.setAttribute('data-date', date.format('YYYY-MM-DD'));
        // 对应的iso星期
        td.setAttribute('data-isoweekday', day);

        // 周末标记text.className
        if (day === 6 || day === 7) {
            td.className += ' ep-calenday-weekend';
        }
        // 非本月标记
        // substr 在ie8下有问题
        // if (month != parseInt(this.currMonth.substr(-2))) {
        if (month != parseInt(this.currMonth.substr(5), 10)) {
            td.className += ' ep-calendar-othermonth';
        }
        // 今天标记
        if (this.today == date.format('YYYY-MM-DD')) {
            td.className += ' ep-calendar-today';
        }

        // 每天渲染时发生 还未插入页面
        var renderEvent = this.fire('cellRender', {
            // 当天的完整日期
            date: date.format('YYYY-MM-DD'),
            // 当天的iso星期
            isoWeekday: day,
            // 日历dom






请到「今天看啥」查看全文