前言:最近看到慕课网上有一个实现日期选择器组件的课程,空闲时间就看了一下,觉得还是有一些借鉴之处,在参考了一下其他文章,写下了这篇文章。通过自己的理解将整个实现过程以最简单的语言描述出来,争取让小白也能看懂。文章中可能会出现许多拓展知识,千万不要错过哦!
文章的知识点:通过原生HTML/CSS/JavaScript完成一个日期选择器(datepicker)组件的开发。主要包括datepicker静态结构的编写、日历数据的计划获取、组件的渲染以及组件事件的处理。
其它参考文章:
天之蓝源:三分钟在GitHub上搭建个人博客zhuanlan.zhihu.com实现效果:
一.什么是日期选择器(datepicker)?
日期选择器在我们的网站或者其他应用上是经常碰到的,它具有让你快速选择日期的功能。日期选择器的类型也是各种各样的,但是总体上就和下图差不多:
我们这次要做的就和上图差不多,记住最重要的一句话:实现过程才是最重要的,理解实现思路以及方法才是这篇文章的重点。
二.组件化开发思想
随着前端技术的发展,组件化开发的思想越来越深入人心,组件化并不是前端所特有的,一些其他的语言或者桌面程序等,都具有组件化的先例。其实总的来说,只要涉及到UI层面的开发,基本都会涉及到组件化开发思想。一个组件就是一个独立的个体,在网站开发中,一个页面可以由多个组件组成,比如说一个按钮可以是一个组件,一个侧边栏可以是一个组件,总之,随着前端技术的发展,组件化开发的思想是不可或缺的。
更多关于组件化可以阅读以下:
https://juejin.im/entry/582d3970d203090067f7de3djuejin.im 关于前端组件化、状态管理规范化的思考 - 掘金juejin.im 前端重构之路(组件化) - 前端 - 掘金juejin.im三.编写页面结构和样式
(一)HTML结构
我们可以看到HTML结构是很简单的,总共就分为了两大部分,一个head部分,一个body部分。这里值得注意的是我们给元素取的类名都比较长,比较特殊,这是因为我们这里是组件化开,所谓组件就是可以重复使用的,使用的场景有很多,所以我们就要尽可能的让我们的类名特殊,这样避免使用的使用重名。
<
此时没有样式的页面黑很丑,但是也能看出大体结构:
更过关于表格结构的知识点请移步:
HTML <table> 标签www.w3school.com.cn(二)添加样式
新增style.css文件
/* 最外层区域 */
样式部分没有什么可说的,都是用的很基本的样式属性,主要就是设置表头和表格的样式。补充一个我比较少接触到的样式属性:
CSS border-collapse 属性www.w3school.com.cn此时的日期选择器的基本结构已经完成:
四.日历中的核心数据
所谓核心数据就是日历中显示每一天的数据,如下所示:
这些数据的作用:
- 渲染当月日历表格
- 用户点击时获取日期值
五.需要事先了解的知识点
(一)日期对象——Data
W3C上的部分说明:
具体详情请移步:
JavaScript Date(日期)对象www.w3school.com.cn这里我们主要是使用以下方式进行传值,因为当用户选择日期的时候,实际上是将点击的值传入Date对象里面,然后获取值:
new Date(year,month-1,date)//注意月份需要-1
注意:日期对象有一个“越界自动进(退)位”的特性。
(二)其他API——getFullYear()/getMonth()/getDate()/getDay()
W3C上的解释:
1.getFullYear()
详情请移步:
JavaScript getFullYear() 方法www.w3school.com.cn2.getMonth()
W3C上的解释:
具体详情请移步:
JavaScript getMonth() 方法www.w3school.com.cn3.getDate()
W3C上的解释:
详情请移步:
JavaScript getDate() 方法www.w3school.com.cn4.getDay()
W3C上的解释:
详情请移步:
JavaScript getDay() 方法www.w3school.com.cn(三)日期对象获取天数
这里为什么会把这个单独拿出来讲一下呢,那肯定是有它令人疑惑的地方:
- 获取当月第一天:
new Date(year,month-1,1)
- 获取当月最后一天
new Date(year,month,0)
- 星期一-星期天
[1,2,3,4,5,6,0]//注意周天不是7而是0
这里我们可以看到获取当月最后一天的时候我们的月份并没有-1,那么就是默认获取的下一个月,然后我们天那里传的0,这里就解释了前面所说的“越界自动进(退)位”。
注意:我们传入的月份的范围:0~11
为什么会有这么奇怪的定义呢?打个比方,我们要获取某年2月份的最后一天,这时候很多人就可能会这样写:
new Date(year,1,28/29)//注意,因为month要-1,所以月份就要填1,才表示获取的2月份
这里大家应该就看得出来了,2月份有多少天是不固定的,所以我们传入值的时候就有可能不知道了,但是我们有了上面的规则就不一样了,我们可以这样写:
new Date(year,2,0)
利用“越界自动进(退)位”的特性,让系统自己去获取最后一天,这样是不是就不用我们瞎操心了,所以,任何事物存在必有它的道理的。
六.编写我们的JavaScript
(一)获取日历数据
我们新建一个data.js文件
(
index.html页面添加如下代码:
<script src="./data.js"></script>
<script>
var monthDate = datepicker.getMonthDate(2019, 2);
console.log(monthDate);
</script>
此时我们看一下打印台上打印的什么:
很明显,这里我们已经打印出来了2月份所有的天数,至于为什么会答应出来这么多天留给大家思考一下。
(二)数据渲染
获取到了数据那么重要的u是渲染到我们的日历当中了.
新建一个main.js
(function () {
var datepicker = window.datepicker;
datepicker.buildUi = function (year, month){
var monthData = datepicker.getMonthDate(year, month);//获取一个月的数据
//由于没有使用第三方插件,所以采用拼接字符串的方式
var html = '<div class="ui-datepicker-header">'+
'<a href="#" class="ui-datepicker-btn ui-datepicker-prev-btn"><</a>'+
'<a href="#" class="ui-datepicker-btn ui-datepicker-next-btn">></a>'+
'<span class="ui-datepicker-curr-month">'+monthData.year+'-'+monthData.month+'</span>'+
'</div>'+
'<div class="ui-datepicker-body">'+
'<table>'+
'<thead>'+
'<tr>'+
'<th>一</th>'+
'<th>二</th>'+
'<th>三</th>'+
'<th>四</th>'+
'<th>五</th>'+
'<th>六</th>'+
'<th>日</th>'+
'</tr>'+
'</thead>'+
'<tbody>';
for(var i=0; i<monthData.days.length; i++){
var date = monthData.days[i];
if(i%7 === 0){
html += '<tr>';
}
html += '<td>' + date.showDate + '</td>'
if(i%7 === 6){
html += '</tr>'
}
}
'<tr>'+
'<td>29</td>'+
'<td>30</td>'+
'<td>1</td>'+
'<td>2</td>'+
'<td>3</td>'+
'<td>4</td>'+
'<td>5</td>'+
'</tr>'
html += '</tbody>'+
'</table>'+
'</div>';
return html;
};
datepicker.init = function ($dom) {
var html = datepicker.buildUi();
$dom.innerHTML = html;
}
})();
index.html页面加上:
<script>
var monthDate = datepicker.getMonthDate(2019, 2);
console.log(monthDate);
datepicker.init(document.querySelector('.ui-datepicker-wrapper'))
</script>
可以看到我们的数据已经能够正常渲染出来了。值得注意的是我们在js里面进行了渲染,那么index.html里面的部分代码就可以不要了,变成这样:
<body>
<div class="ui-datepicker-wrapper">
</div>
</body>
<script src="./data.js"></script>
<script src="./main.js"></script>
<script>
var monthDate = datepicker.getMonthDate(2019, 2);
console.log(monthDate);
datepicker.init(document.querySelector('.ui-datepicker-wrapper'))
</script>
注意:我们这里没有引用第三方插件或库,所以我们渲染的时候用的是字符串拼接,但是实践中通常采用的是第三方插件或库,更多知识可以参考:
前端模板与渲染方式 - axl234 - 博客园www.cnblogs.com(三)细节修改
我们都知道很多日期选择器的样式都是一个选择框,点击选择框然后才弹出日历,这里我们还没有实现,所以我们现在来改一下:
此时的index.html变成这样:
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<style>
.datepicker {
border: 1px solid #ccc;
border-radius: 4px;
width: 230px;
padding: 5px;
line-height: 24px;
}
.datepicker:focus {
outline: none;
border: 1px solid #1abc9c;
}
</style>
</head>
<body>
<input type="text" class="datepicker">
</body>
<script src="./data.js"></script>
<script src="./main.js"></script>
<script>
// var monthDate = datepicker.getMonthDate(2019, 2);
// console.log(monthDate);
// datepicker.init(document.querySelector('.ui-datepicker-wrapper'))
datepicker.init('.datepicker');
</script>
</html>
然后修改一下main.js,动态的来创建我们的div:
datepicker.init = function (input) {
var html = datepicker.buildUi();
// document.body.innerHTML = html;
var $wrappper = document.createElement('div');
$wrappper.className = 'ui-datepicker-wrapper';
$wrappper.innerHTML = html;
document.body.appendChild($wrappper);
}
此时页面上多了一个文本框,但是此时我们的页面上已经没有了div了,整个包含日历的div元素由我们的js代码来创建。
(四)日历的展开收起
我们可以看到一进页面的时候日历就已经存在了,一般情况下是需要点击input框的时候日历才会显示元素,而且我们需要采用定位的方式来对日历进行限定,因为页面上有其他元素,如果不采用定位的 话就会影响到其他元素。
在style.css里面添加一个类用来控制显示或者隐藏:
.ui-datepicker-wrapper {
........
display: none;//添加默认隐藏
position: absolute;//添加绝对定位
}
.ui-datepicker-wrapper-show {
display: block;
}
此时我们在main.js里面的init函数里面设置显示或者隐藏,并且根据input框的位置动态的给日历添加top和left值,这样可以适用于多种场景,此时init函数变为:
datepicker.init = function (input) {
var html = datepicker.buildUi();
// document.body.innerHTML = html;
var $wrappper = document.createElement('div');
$wrappper.className = 'ui-datepicker-wrapper';
$wrappper.innerHTML = html;
document.body.appendChild($wrappper);
//控制显示或者隐藏
var $input = document.querySelector(input);
var isOpen = false;
$input.addEventListener('click',function(){
if(isOpen){
$wrappper.classList.remove('ui-datepicker-wrapper-show');
isOpen = false;
}else {
$wrappper.classList.add('ui-datepicker-wrapper-show');
//获取input的位置,设置日历的位置
var left = $input.offsetTop;
var top = $input.offsetTop;
var height = $input.offsetHeight;
$wrappper.style.top = top + height + 2 + 'px';
$wrappper.style.left = left + 'px';
isOpen =true;
}
},false)
}
这样编写之后,我们便能通过点击输入框实现日历的显示或者隐藏了,而且也能通过定位的方式来确定日历的位置。
点击之后:
(五)月份切换和日期选择
我们将实现月份切换的逻辑也放在init函数里面:
这里值得注意的是:我们的init函数只执行了一次,如果我们直接把点击事件绑定在btn上面,那么事件就只有在渲染页面的时候才会初始化一次,意味着只绑定了一次,但是在我们渲染之后,我们的按钮每一次都是根据html字符串重新渲染出来的,也就是我们的按钮会不断的销毁和重建,所以我们绑定的事件是无法生效的。所以我们这里采用将时间绑定在不变的外层元素wrapper上。
这里修改的地方较多,最终的main.js代码如下:
(
这里改动的地方较多,主要增加的是日期的权责和两个按钮事件,期间将一些逻辑单独提出来作为了方法,由于担心讲解的改动地方的时候讲漏,所以直接贴出main.js的最终代码。
四.总结
主要实现步骤:
- 使用HTML合理规划组件结构
- 为组件编写美观的样式
- 如何使用Javascript获取组件所需要的数据
- 将数据与HTML结构结合
- 用户事件处理
到这里为止,日期选择组件就完成了,总的来说,实现过程较为复杂,但是只要你细心,只要你肯思考,一阵代码下来你肯定收获是非常多的。 另外,在我编写文章的时候有可能写漏或者写错,导致代码出现错误,特别是最后添加按钮点击事件和添加选择日期功能,这两步改动较多,所以笔者没有一一说明,容易导致错误或不理解,请不要卡壳,继续往下走,到后面你就会发现问题所在。此篇文章也是笔者边看慕课网上的视频自己边总结的,深刻理解其中的代码逻辑等。
源代码请移步:
Hacker233/JavaScriptgithub.com