JavaScript日历(es5版本)

近期在知乎上看到这么一个帖子,题主说自己JavaScript都学完了,结果老师留的作业还是不会写,就是写一个日历的插件,结果楼下一堆大牛出现了,百度的阿里的纷纷站出来发表自己的看法,有人认为简单,有人认为其实细化不简单,于是出于好奇,自己也来动手写了一下。虽说现如今各种优秀的UI框架层出不穷,都会自带calendar和datepick这种日历相关的组件,而且无论是适配还是视觉效果都做得相当nice,可能都不会用到自己写的,但是还是打算动手,因为date对象也是js里面的重点。

第一版:纯js实现

通过切换月份和年份,来展示不同的日历页面,实际上是根据当前年月,来进行页面的重绘,所以页面渲染是一个函数,所需参数就是当前选中的年份和月份。

const render = (month, year) => {

};

render();

首先,日历除去首行day有几行?
一共6行,Math.ceil((31 - 1) / 7 + 1) = 6


三块组成:上月剩余,本月,下月开始

1,上月剩余
首先要知道本月1号是周几(x),然后从周日到x的天数就是上月剩余天数,从几号到几号,需要了解本月是几月,来推算出上月月末是几号,其实也就是上月有多少天。

2,本月
只需知道当月是几月,当月多少天,然后按顺序排。

3,下月开始
只需要知道下月1号是周几,然后42个数字还剩多少,从1排到最后就可以了
 
代码部分:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Date Picker</title>
    <link rel="stylesheet" type="text/css" href="css/index.css"></link>
</head>
<body>
    <input id="textInput" class="textInput" type="text" placeholder="选择日期" />
    <div id="datePicker" class="datePicker datePickerHide">
        <div class="dateHeader">
            <span id="yearLeft" class="left headerMid"><<</span>
            <span id="monthLeft" class="left headerMid page"><</span>
            <span id="changeDateHead" class="page">
                <span id="changeYear" class="headerMid col">
                    <span id="dateYear"></span>
                    <span>年</span>
                </span>
                <span id="changeMonth" class="headerMid col">
                    <span id="dateMonth"></span>
                    <span>月</span>
                </span>
            </span>
            <span id="changeYearHead" class="page headerMid col" style="display: none">
                <span id="changeYearFirst"></span>
                <span>年</span>
                <span>-</span>
                <span id="changeYearLast"></span>
                <span>年</span>
            </span>
            <span id="changeMonthHead" class="page  headerMid col" style="display: none">
                <span id="backChangeYearPage"></span>
                <span>年</span>
            </span>
            <span id="yearRight" class="right headerMid">>></span>
            <span id="monthRight" class="right headerMid page">></span>
        </div>
        <div class="dateMain">
            <div id="firstPage" class="page firstPage" style="display: block">
                <div class="dateDay">
                    <span>日</span>
                    <span>一</span>
                    <span>二</span>
                    <span>三</span>
                    <span>四</span>
                    <span>五</span>
                    <span>六</span>
                </div>
                <div id="dateBody" class="dateBody"></div>
            </div>
            <div id="secondPage" class="page secondPage" style="display: none"></div>
            <div id="thirdPage" class="page secondPage" style="display: none" onclick="chooseMonth()">
                    <span><em id="month-1" index='1'>1月</em></span>
                    <span><em id="month-2" index='2'>2月</em></span>
                    <span><em id="month-3" index='3'>3月</em></span>
                    <span><em id="month-4" index='4'>4月</em></span>
                    <span><em id="month-5" index='5'>5月</em></span>
                    <span><em id="month-6" index='6'>6月</em></span>
                    <span><em id="month-7" index='7'>7月</em></span>
                    <span><em id="month-8" index='8'>8月</em></span>
                    <span><em id="month-9" index='9'>9月</em></span>
                    <span><em id="month-10" index='10'>10月</em></span>
                    <span><em id="month-11" index='11'>11月</em></span>
                    <span><em id="month-12" index='12'>12月</em></span>
            </div>
        </div>
    </div>
</body>
<script src="js/index.js"></script>
</html>

  

js部分:

    // 默认是当天
var chosenDate = new Date(),
    year = chosenDate.getFullYear(),
    month = chosenDate.getMonth() + 1,
    date = chosenDate.getDate(),
    lastDateId = '',    // 挂到全局下去
    lastYearId = '',
    lastMonthId = '',
    firstNum = 0;

window.onload = function(){

    renderFirstPage(year, month, date);

    

    
    // input框获取焦点时日历显示

    var datePicker = getIdDom('datePicker');

    getIdDom('textInput').onfocus = function(){
        datePicker.className = 'datePicker datePickerShow';
    }


    /* 以上是第一部分页面展示  */

    /* 二级页面 */
    var renderSecondPage = function(firstNum){
        var lastNum = firstNum + 9;     // 二级页面末尾数字
        getIdDom('changeYearFirst').innerHTML = firstNum;
        getIdDom('changeYearLast').innerHTML = lastNum;
    
        var yearTemplate = ``;
        for (var i = firstNum; i < lastNum + 1; i++) {
            if (i == year) {
                // 当前选中年
                yearTemplate += `<span><em id="year-${i}" onclick="chooseYear(this, ${i})" style="background-color: #39f;color: #fff">${i}</em></span>`
            } else {
                yearTemplate += `<span><em id="year-${i}" onclick="chooseYear(this, ${i})">${i}</em></span>`
            }
        }
        getIdDom('secondPage').innerHTML = yearTemplate;
    }

    var reRenderSecondPage = function(){
        var yearStr = year.toString();
        var yearLastLetter = yearStr[yearStr.length-1];     // 末尾数
        firstNum = year - Number(yearLastLetter);   // 二级页面首位数字
        renderSecondPage(firstNum);
    }
    reRenderSecondPage();
    

    getIdDom("backChangeYearPage").innerHTML = year;     // 三级页面年份

    // click事件集中写
    
    // 上一年下一年,上一月下一月的点击事件
    clickFn('yearLeft', function(){
        if (getIdDom('changeYearHead').style.display != 'none') {
            // 此时是二级页面,选年份
            if (firstNum - 10 < 1) {
                // 首位年份不能小于1
                return
            }
            firstNum -= 10;
            renderSecondPage(firstNum);
        } else {
            if (year - 1 < 1) {
                // 年份不能小于1
                return
            }
            year--;
            renderFirstPage(year, month, date);
            getIdDom("backChangeYearPage").innerHTML = year;
            reRenderSecondPage();
        }
    });

    clickFn('monthLeft', function(){
        if (month < 2) {
            // 1月
            month = 12;
            year--;
        } else {
            month--;
        }
        renderFirstPage(year, month, date)
    });

    clickFn('yearRight', function(){
        if (getIdDom('changeYearHead').style.display != 'none') {
            // 此时是二级页面,选年份
            firstNum += 10;
            renderSecondPage(firstNum);
        } else {
            year++;
            renderFirstPage(year, month, date);
            getIdDom("backChangeYearPage").innerHTML = year;
            reRenderSecondPage();
        }
    });

    clickFn('monthRight', function(){
        if (month > 11) {
            // 12月
            month = 1;
            year++;
        } else {
            month++;
        }
        renderFirstPage(year, month, date)
    });

    
    clickFn('changeYear', function(){
        var pagesArr = Array.from(document.querySelectorAll('.page'));
        pagesArr.forEach(function(item){
            item.style = 'display: none'
        });
        reRenderSecondPage();
        changeStyle('secondPage', 'display: block');
        changeStyle('changeYearHead', 'display: inline-block');
    });     // 点击年份切换至二级页面

    clickFn('changeMonth', function(){
        var pagesArr = Array.from(document.querySelectorAll('.page'));
        pagesArr.forEach(function(item){
            item.style = 'display: none'
        });
        if (lastMonthId !== '') {
            // 非第一次点击
            getIdDom(lastMonthId).style = "";
        }
        changeStyle("month-" + month, 'background-color: #39f;color: #fff');
        lastMonthId = 'month-' + month;
        changeStyle('thirdPage', 'display: block');
        changeStyle('changeMonthHead', 'display: inline-block');
    })

    clickFn('changeMonthHead', function(){
        // 切回年份选择
        var pagesArr = Array.from(document.querySelectorAll('.page'));
        pagesArr.forEach(function(item){
            item.style = 'display: none'
        });
        changeStyle('secondPage', 'display: block');
        changeStyle('changeYearHead', 'display: inline-block');
        reRenderSecondPage();
    })

    document.getElementsByTagName('html')[0].onclick = function(e){
        // 这里模拟失去焦点事件
        var name = e.target.nodeName;
        if (name == 'BODY' || name == 'HTML') {
            datePicker.className = 'datePicker datePickerHide';
        }
    }

}


function getIdDom(id){
    return document.getElementById(id)
}  // 根据id获取dom节点

function clickFn(id, fn) {
    getIdDom(id).onclick = fn;
}  // 封装一下click方法

function renderFirstPage(year, month, date = 1){
    var datePage = [];    // 最终展示页面的所有日期合集        
    // 第一部分,上月月末几天
    // 首先要知道上月一共多少天
    var getAlldays = (year, month) => {
        if (month <= 7) {
            // 1-7月
            if (month % 2 === 0) {
                // 偶数月
                if (month === 2) {
                    // 2月特殊
                    if ((year % 400 == 0) || ( year % 4 == 0 && year % 100 != 0)) {
                        // 闰年
                        var alldays = 29
                    } else {
                        var alldays = 28
                    }
                } else {
                    var alldays = 30
                }
            } else {
                // 奇数月
                var alldays = 31
            }
        } else {
            // 8-12月
            if (month % 2 === 0) {
                // 偶数月
                var alldays = 31
            } else {
                var alldays = 30
            }
        }
        return alldays
    };      // alldays表示某年某月的总天数
    var lastMonthAllDays = getAlldays(year, month - 1);    // 上月天数
    var chosenFirstMonthDay = new Date(year, month - 1, 1).getDay();    // 当月1号周几

    var datePageTemplate = ``;
    var num = 0;

    for (var i = lastMonthAllDays - chosenFirstMonthDay + 1; i < lastMonthAllDays + 1; i++ ) {
        datePageTemplate += `<span id="lastMonth"><em id="last-${i}" onclick="chooseDate(this, 'last-${i}', ${year}, ${month}, ${i})">${i}</em></span>`;
        num++;
    }

    // 第二部分,当月总天数
    var chosenMonthAllDays = getAlldays(year, month);  // 当月天数
    var time = new Date();
    var a = time.getFullYear(),
        b = time.getMonth() + 1,
        c = time.getDate(); // 用来记录当天时间
    for(var i = 1; i < chosenMonthAllDays + 1; i++) {
        var chosenDateObj = {
            year: year,
            month: month,
            date: i
        };
        if (i === c && year === a && month === b) {
            // 今天日期高亮
            datePageTemplate += `<span id="today" class="currentMonth"><em id="today-${i}" onclick="chooseDate(this, 'today-${i}', ${year}, ${month}, ${i})" class="today">${i}</em></span>`;
        } else {
            datePageTemplate += `<span id="currentMonth" class="currentMonth"><em id="cur-${i}" onclick="chooseDate(this, 'cur-${i}', ${year}, ${month}, ${i})">${i}</em></span>`;
        }
        
        num++;
    }

    // 第三部分,剩余天数
    for (var i = 1; i < 43 - num; i++) {
        var chosenDateObj = {
            year: year,
            month: month,
            date: i
        };
        datePageTemplate += `<span id="nextMonth"><em id="nex-${i}" onclick="chooseDate(this, 'nex-${i}', ${year}, ${month}, ${i})">${i}</em></span>`;
    }


    var templateString = `${datePageTemplate}`;

    var innerFn = function(id, content) {
        getIdDom(id).innerHTML = content
    };
    innerFn('dateYear', year);
    innerFn('dateMonth', month);
    innerFn('dateBody', templateString);

}

function chooseDate(item, index, year, month, date) {
    event.stopPropagation();
    if (lastDateId !== '') {
        // 非第一次点击
        getIdDom(lastDateId).style = "";
    }
    // 选中项样式改变,并且将input的日期修改
    lastDateId = index;
    item.style = "background-color: #39f;color: #fff";
    getIdDom("textInput").value = year + '-' + month + '-' + date;
}

function chooseYear(item, thisYear) {
    event.stopPropagation();
    if (lastYearId !== '') {
        // 非第一次点击
        if (getIdDom(lastYearId)) {
            // 存在已经跨页面了,但是id不存在了
            getIdDom(lastYearId).style = "";
        }
    } else {
        // 第一次点击
        getIdDom('year-' + year).style = "";
        
    }
    lastYearId = 'year-' + thisYear;
    year = thisYear;
    item.style = "background-color: #39f;color: #fff";
    var pagesArr = Array.from(document.querySelectorAll('.page'));
    pagesArr.forEach(function(item){
        item.style = 'display: none'
    });
    if (lastMonthId !== '') {
        // 非第一次点击
        getIdDom(lastMonthId).style = "";
    }
    changeStyle("month-" + month, 'background-color: #39f;color: #fff');
    lastMonthId = 'month-' + month;
    getIdDom("backChangeYearPage").innerHTML = year;
    changeStyle('changeMonthHead', 'display: inline-block');
    changeStyle('thirdPage', 'display: block');
}

function chooseMonth(){
    var target = event.target;
    if (target.nodeName === 'EM') {
        // 表示当前点击的为em节点
        if (lastMonthId !== '') {
            // 非第一次点击
            getIdDom(lastMonthId).style = "";
        } else {
            // 第一次点击
            getIdDom('month-' + month).style = "";
        }
        month = parseInt(target.innerHTML);
        lastMonthId = 'month-' + month;
        target.style = "background-color: #39f;color: #fff";

        renderFirstPage(year, month, date);

        // 展示首页
        var pagesArr = Array.from(document.querySelectorAll('.page'));
        pagesArr.forEach(function(item){
            item.style = 'display: none'
        });
        changeStyle('firstPage', 'display: block');
        changeStyle(['changeDateHead', 'monthLeft', 'monthRight'], 'display: inline-block');
    }
}

// 判断对象类型
function isType(type){
    return function(obj){
        return toString.call(obj) == '[object ' + type + ']';
    }
}

// 改变display属性
function changeStyle(ids, styles){
    var isString = isType('String'),
        isArray = isType('Array');
    if (isString(ids)) {
        getIdDom(ids).style = styles;
    } else if (isArray(ids)) {
        ids.forEach(function(item){
            getIdDom(item).style = styles;
        })
    }
}

  

  

css部分:

* {
    margin: 0;
    padding: 0;
}

*, :after, :before {
    box-sizing: border-box;
}

body {
    font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,\5FAE8F6F96C59ED1,Arial,sans-serif;
}

.textInput {
    position: relative;
    display: block;
}

.datePicker {
     216px;
    margin: 10px;
    color: #c3cbd6;
    border-radius: 4px;
    box-shadow: 0 1px 6px rgba(0,0,0,.2);
    transform-origin: center top 0px;
    transition: all .2s ease-in-out;
    position: absolute;
    left: 0px;
    top: 16px;
}
.datePickerHide {
    opacity: 0;
}

.datePickerShow {
    opacity: 1;
}

.dateHeader {
    height: 32px;
    line-height: 32px;
    text-align: center;
    border-bottom: 1px solid #e3e8ee;
}

.left, .right {
    display: inline-block;
     20px;
    height: 24px;
    line-height: 26px;
    margin-top: 4px;
    text-align: center;
    cursor: pointer;
    color: #c3cbd6;
    -webkit-transition: color .2s ease-in-out;
    transition: color .2s ease-in-out;
}

.left {
    float: left;
    margin-left: 10px;
}

.right {
    float: right;
    margin-right: 10px;
}

.dateMain {
    margin: 10px;
}

.dateDay {
    line-height: normal;
    font-size: 0;
    letter-spacing:normal;
}

span {
    display: inline-block;
    text-align: center;
    font-size: 12px;
}

.dateDay span {
    line-height: 24px;
     24px;
    height: 24px;
    margin: 2px;
}

.dateBody span {
     28px;
    height: 28px;
    cursor: pointer;   
}

.dateBody span em {
    display: inline-block;
    position: relative;
     24px;
    height: 24px;
    line-height: 24px;
    margin: 2px;
    font-style: normal;
    border-radius: 3px;
    text-align: center;
    transition: all .2s ease-in-out;
}

.dateBody span.currentMonth {
    color: #657180;
}

.today:after {
    content: '';
    display: block;
     6px;
    height: 6px;
    border-radius: 50%;
    background: #39f;
    position: absolute;
    top: 1px;
    right: 1px;
}

.currentMonth em:hover {
    background-color: #e1f0fe;
}

.col {
    color: #657180;
}

.headerMid {
    cursor: pointer;
}

.headerMid:hover {
    color: #39f;
}

/* second */
.secondPage {
     196px;
    font-size: 0;
}

.secondPage span {
    display: inline-block;
     40px;
    height: 28px;
    line-height: 28px;
    margin: 10px 12px;
    border-radius: 3px;
    cursor: pointer;
}

.secondPage span em {
    display: inline-block;
     40px;
    height: 28px;
    line-height: 28px;
    margin: 0;
    font-style: normal;
    border-radius: 3px;
    text-align: center;
    transition: all .2s ease-in-out;
    color: #657180;
}

.secondPage span em:hover {
    background-color: #e1f0fe;
}

  

GitHub项目地址:https://github.com/Yanchenyu/DatePicker

 
 
项目写完了,但其实发现里面存在大量的DOM操作以及环境污染,这个对性能的损耗是相当大的,写得越多越发现状态难以管理,都只能挂到全局下去,所以打算再写一套React版本的。
 
 
 
 
 
 
 
 
 
 
end
原文地址:https://www.cnblogs.com/yanchenyu/p/8425141.html