以前收藏了一篇自己动手实现日期选择器的插件,最近没什么事,就想着仿照ElementUI的DatePicker,自己也写了一个简易的日期选择器,本以为不会很麻烦,实际动手才发现有很多问题需要解决。并且在写完之后,才发现可扩展性很差,距离ElementUI的水平差的很远,下一步就是把ElementUI的源码学习一下,看清楚自己的差距。

结构

我将这个日期选择做成了Vue的插件的形式,有三个文件:

- index.js
- MyDate.js
- MyDatePicker.vue

index.js很简单,只有一个方法intstall,在install里面注册了全局组件:

import MyDatePicker from './MyDatePicker'export default {install(Vue) {Vue.component('MyDatePicker', MyDatePicker)}
}

MyDatePicker.vue是UI部分,在这里定义样式和交互事件,我将数据单独放到了MyDate.js中,以Class形式导出

数据部分

我的顺序是先完成MyDate.js的数据部分,一个日期选择器基本结构如下:

最后导出一个二维数组,二维数组外层包含6个数组,对应日期选择器中的每一行的数据,内层数组又包含7个对象元素,对应周一到周日,这样相当于总共有42个元素,正好对应面板中的42个日期。

当选择一个日期后,首先通过new Date构造函数获得当前日期所在月的第一天及这一天是星期几

// 当前选择日期所在月的第一天及这一天是星期几
const firstDayOfCurrentMonth = new Date(this.current.year, this.current.month - 1);
const firstDayOfWeek = firstDayOfCurrentMonth.getDay();

要注意的是,new Date().getMonth()的范围是[0, 11],和我们日常使用的月份是少1的。而我在current里面存的日期是为了显示所用的,已经加过1了,所以需要在上面减1

接下来,通过两层的遍历来生成我们所需要的二维数组,外层遍历是对应的是行数据:

for (let row = 0; row < 6; row++) {
}

关键是内层数据,假设我们选择的就是2019年6月,6月1日是星期六,在二维数组的内部数组里面的七个元素,应该吻别对应['日', '一', '二', '三', '四', '五', '六'],现在6月1日星期六,它位于数组的第七项,补齐这个数组的结果应该是:

[5.26, 5.27, 5.28, 5.29, 5.30, 5.31, 6.1]

JS的Date构造函数会自动对超出当前月份的日期进行转换,意思就是,当我们构造一个日期new Date(2019, 5, 0),它会自动往前倒一天,生成的日期就是2019-05-31

所以上面的数组转为对应的以6月为天数就是:

[-5, -4, -3, -2, -1, 0, 1]

所以当前遍历的范围就是[-5, 1],起始点与6月1日的星期几存在这样的关系:

// 内层遍历起始点
let weekLoopStart = -firstDayOfWeek + 1; // -5

结束点是7 + weekLoopStart - 1

这样当内层遍历结束一次时,将weekLoopStart7,就可以生成新的一行数据了:

[2, 3 4, 5, 6, 7, 8]

所以两层遍历的基本形式就是:

// 行数据
for (let row = 0; row < 6; row++) {const rowDate = [];// 列数据for (let weekDay = weekLoopStart; weekDay <= 7 + weekLoopStart - 1; weekDay++) {// 生成需要的对象}weekLoopStart += 7;this.dates.push(rowDate)
}

有点绕,而且可扩展性也不是很好,还是太笨了。

在内层遍历生成的对象有这样几个属性:

const targetDate = new Date(this.current.year, this.current.month - 1, weekDay);
const day = targetDate.getDate();
const month = targetDate.getMonth() + 1;
rowDate.push({date: targetDate,value: format(targetDate),label: day,key: weekDay,isCurrentMonth: month === this.current.month,isToday: +targetDate === +this.today
})

date是标准的日期对象,value是选择后用于展示的格式化的日期,label是在日历中选择的日期,key是整个遍历过程中它实际的标号,isCurrent用来判断这个日期是否属于当前月份,还是以-5这样的格式转换为前一个月的日期(这样的日期在面板上是有不同的样式),isToday用来标识今天的日期:

这样就生成了一个二维数组放到了this.dates这个实例属性中

当改变选择的月份,面板的日期也会随着变化,对应的实例方法就是changeDate,因为刚才的生成数据的getDateArray方法都是依赖于this.current来进行的,所以只需要改变this.current的值,然后重新执行getDateArray方法就行了

// 改变日期
changeDate(date = new Date()) {this.current = {year: date.getFullYear(),month: date.getMonth() + 1};this.getDateArray()
}

这样基本的数据就完成了。

UI部分

UI部分是在.vue的单文件组件完成的,面板使用了<table>标签,在date里面引入MyDate的实例,其余都声明为计算属性,与MaDate的实例相关联,这样形成了这样的变化过程:

有三个事情需要记录一下

(1)设定单元格样式

<tr v-for="(row, rowIndex) in tbody" :key="'row-'  + rowIndex"><td v-for="cell in row" :key="cell.key" @mousedown="selectDate(cell)"><span :class="tableCellClass(cell)">{{cell.label}}</span></td>
</tr>

因为单元格的原始和遍历的数据cell有关系,如果卸载模板中会有一大堆的代码,不太直观,用计算属性生成一个对象有没有办法传入参数,所以可以用一个method,返回一个对象传给:class

// 设定日期单元格样式
tableCellClass(cell) {return {'not-current-month': !cell.isCurrentMonth,today: cell.isToday,selected: cell.value === this.selectedDate}
},

(2)动画效果

ElementUI的动画效果是向上滑出

它是通过Vue的<transition>组件实现的,而<transition>是用JS实现的动画,使用了requestAnimationFrame的API,很流畅,而且便于复用。找个时间还是要好好看一些Vue的源码,学习一下。

我使用了CSS动画来实现,当选择框出现时,添加一个类container-visible,将原本的height0改为320px,同时将opacity0改为1,同时添加了will-changetransform: translateZ(0)来提升性能:

.date-container {position: absolute;left: 0;top: 46px;color: #606266;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);background: #fff;border-radius: 4px;line-height: 30px;margin: 5px 0;transition: all 0.5s ease;border: 1px solid #e4e7ed;height: 0;overflow: hidden;will-change: height;opacity: 0;transform: translateZ(0);
}
.container-visible {height: 320px;opacity: 1;
}

实现的效果还可以,但是有两个问题,一是不太好复用,而是需要改为固定的高度,如果面板高度变化,效果就有可能有偏差

(3)第三个问题是日期选择框的出现和隐藏,它具体的逻辑如下:

  1. 点击输入框,出现选择框
  2. 点击输入框和选择框之外的部分,选择框消失
  3. 点击输入框和选择框之内的部分,选择框不消失
  4. 点击选择框的快速选择月份(那几个小箭头),选择框发生相应改变,不消失
  5. 点击选择框的具体日期,选择框消失,选择成功

我选用的方案是使用<input>focusblur事件,发生两个事件时,改变控制选择框是否显示的变量containerVisible

focus没有问题,但是blur有着比较大的问题,首先遇到的问题时,当点击选择框的按钮功能和时期时,没有触发对应的功能,选择框就消失了(以前在开发业务的时候遇到过类似的问题),这主要是因为blur事件发生的时机:

click事件发生之前blur事件就发生了,导致click事件没有发生时,元素就隐藏了,click事件无效。

所以像以前一样,将选择框绑定的click事件改为了mousedown事件,这样做的效果是,点击日期能够选择了,并且选择事件执行了,并且之后选择框失效了,这时候上面的五条逻辑满足了1/2/5,但是3/4又出问题了,点击选择框的小按钮,选择框意外消失了。

之所以这样,是因为mousedown事件之后,blur事件执行,导致选择框小事,我们要做的是在mousedown之后,不触发blur事件,所以应该使用peventDefault方法(注意不是stopImmdeiation,因为不是冒泡导致的),Vue中提供的修饰符是prevent,所以在所有的mousedown事件后面添加上修饰符prevnet

<button type="button" class="btn next-month-btn" @mousedown="changeMonth(1)"><span class="iconfont icon-el-icon-arrow-right"></span>
</button>

这样条件4满足了,但是3不行,所以需要在整个选择框的容器上添加一个mousedonw事件,并且使用prevnet修饰符,里面的点击事件只需要使用mousedown就可以了

<div class="date-container" :class="{'container-visible': containerVisible}" @mousedown.prevent>
</div>

这样基本上就成功了,但是还是有一些小瑕疵,一个问题就是blur事件发生的过于容易,比如我点击浏览器之外的桌面部分,blur事件也会发生,选择框会消失,而ElementUI的并不会消失,还有就是绑定了没有必要的点击事件,不好复用,并且不知道如果同时有多个弹出框的时候还不会有其他的问题。

ElementUI是把这块单独提出了一个方法,位于element/src/utils/clickoutside.js,它对这种情况的点击事件做了统一的处理,主要的思路就是在document绑定了统一的点击事件,通过收集此刻的弹窗元素到一个队列中,隐藏这个队列中的元素,它没有使用blur事件,更可控,也更适合更多的元素。

优化

这个日期选择器插件的基本功能能够满足,但是如果作为ElementUI那样的轮子,还差的很多,扩展非常困难(快速选择月、年的面板我就没有做)

下一步的计划就是首先学习clickoutside的实现,然后学习ElementUI的源码,这个计划也好久了,要尽快执行啊~

最后,代码都在这里。

Vue提高20 日期选择器插件相关推荐

  1. 【vue开发问题-解决方法】(五)vue Element UI 日期选择器获取日期格式问题 t.getTime is not a function

    [vue开发问题-解决方法](五)vue Element UI 日期选择器获取日期格式问题 t.getTime is not a function 参考文章: (1)[vue开发问题-解决方法](五) ...

  2. Datepicker日期选择器插件

    ##Datepicker日期选择器插件 这个插件还是比较简单的,而且样式也比较漂亮,可以自定义选择年月日.年月日时分.年月.时间段选择等等. ###效果预览: #####这个插件是基于jQuery和b ...

  3. EXCEL 2003-2016 日期选择器插件 支持64位系统

    EXCEL 2003-2016 日期选择器插件 支持64位 使用简单且漂亮的EXCEL日期选择器插件 效果如下: Download now for Excel 2007 – 2016 Download ...

  4. html备忘录时间提醒,jQuery简单带备忘录功能的日期选择器插件

    jQuery.Simple-EventCalendar是一款带备忘录功能的jQuery日期选择器插件.你可以在某个日期上记录需要做的事情或已经做的事情,就像工作中的备忘录一样,十分方便. bower ...

  5. 最好用的 12 款 Vue Timepicker 时间日期选择器测评推荐 - 卡拉云

    本文首发:<最好用的 12 款 Vue Timepicker 时间日期选择器测评推荐 - 卡拉云> Vue 时间日期选择器(date-timepicker)组件在使用 Vue 框架开发中使 ...

  6. 原生js实现日期选择器插件 简单风格日期选择插件

    原生js实现日期选择插件 xs-DatePicker ** 效果图 ** ** 简介 通过原生HTML/CSS/JavaScript完成一个日期选择器. 代码如下: ** <!-- 时间:202 ...

  7. Vue日期选择器插件(vue-datepicker-local)

    参考文档: https://github.com/weifeiyue/vue-datepicker-local 支持年份选择器,月份选择器,日期选择器,时间选择器,且均支持范围选择 效果如下演示年月日 ...

  8. bootstrap4日期选择器插件tempusdominus-bootstrap-4具体实例

    先上图 代码 <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en&qu ...

  9. html时间选择控件 简书,flatpickr好看的日期选择器插件

    百度盘链接: https://pan.baidu.com/s/1c2GRktU 密码: jf3v (主要需要的都在dist这个文件夹内) 1.导入日期选择器的css样式(底部有各种颜色对应的样式) d ...

  10. 用 Vue 实现原生日期选择器

    效果: 部分代码 .vue <template><div class="date-select"><div class="date-sele ...

最新文章

  1. Allan方差分析方法的直观理解
  2. Trie树详解及其应用
  3. 3.11 总结-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  4. Android自定义控件(三)——有弹性的ListView
  5. Matlab | Matlab从入门到放弃(5)——矩阵与format
  6. Python爬虫1-----------placekitten 入门
  7. 数据库面试题目经典大全
  8. Educational Codeforces Round 80 (Rated for Div. 2) 二分 + 状压
  9. 数据同步到redis中时候需要 需要给关联的表增加id 如果是一对多 则增加list存储id 如果是一个 则增加一个字段 ;目的是便于取值...
  10. CryptoQuant CEO:比特币大规模从Coinbase流出是最强劲的看涨信号
  11. 51nod 1050循环数组最大字段和
  12. 原生html使用element组件,使用element-ui的table组件时,渲染为html格式
  13. VHDL_EDA课设_八音电子琴
  14. 梅宏:不容错过的大数据时代_我们错过了整个网络支付领域:如何为创作者修复网络...
  15. Mapstruct使用介绍
  16. css实现固定宽高比例的div
  17. android 排他button,javascript排他思想
  18. arch使用create_ap创建wifi热点
  19. 【项目配置学习笔记】启动Tomcat的常出现的错误及其解决办法
  20. 一位软件工程师的6年总结

热门文章

  1. 前路钉板系统在重建胸腰段稳定性中应用 [已发表]
  2. 相关系数excel_给你一份数据,如何去分析?手把手教你用Excel进行数据分析
  3. 如何入门短视频剪辑?先要有五大思维,有想法才能有如神助
  4. WinForm 去掉DataGridView最后一行的空白行,删除空白行
  5. java发微信字体颜色_java微信公众号发送消息模板
  6. Excel,WPS如何同时冻结行与列
  7. [NOI 2014]起床困难综合症
  8. MSM8953 Android9.0 配置USB2.0 Camera
  9. 微信如何html动图文件,微信里的gif动态图片如何导出保存出来?看完本文你就知道了...
  10. 【学习记录】SLAM线特征基础:LSD算法、LBD描述子、普朗克坐标、EDLines算法