zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    黄聪:获取当天最新信息的mysql语句php
    黄聪:开源PHP智能中文分词扩展:PHPCWS
    黄聪:Wordpress用get_current_screen函数来选择性加载插件中的JS和CSS
    黄聪: Javascript跨域访问解决方案
    黄聪:WordPress 角色和权限指南
    黄聪:PHP自动判断字符串是gb2312还是utf8编码
    黄聪:mysql查询今天,昨天,近7天,近30天,本月,上一月数据的方法
    黄聪:python中defaultdict标准字典的使用
    黄聪:IE6下css大bug:文字神秘消失,鼠标选择了才出现
    黄聪:C#模拟网站页面POST数据提交表单(一)WebClient (转)
  • 原文地址:https://www.cnblogs.com/yanchenyu/p/8425141.html
Copyright © 2011-2022 走看看