摸魚不如摸一個動態滿滿的可拖拽日曆時間浮窗組件

前言

時間日曆控件不管在Pc端仍是移動端,都是十分重要且有用的一個組件,最近重拾了本身的UI庫,按照ElementUI的組件種類,以Pc端爲核心進行組件開發【 適配Vue + Less 】,目前項目已有近20個組件,若是你們有興趣,歡迎來GIT上踩踩哦vue

附上GIt地址 - github.com/Jason9708/C…git

本文帶來的日曆控件與以往的日曆控件不太相同,我將以Window自帶的日曆做爲大概的UI設計,摸出一個可拖拽的浮窗型日曆控件github

效果以下:web

需求分析

  • 浮窗可拖拽,實時顯示當前時間
  • 日曆能夠查詢上個月和下個月,返回今天(日期爲今天會有顏色標記)
  • 點擊日期,左側彈出彈窗,彈窗內容可由使用者自定義,超出高度寬度顯示滾動條
  • 覆蓋滾動條樣式,實現天然交互動畫

代碼編寫

文件目錄

  • index.js:工具函數文件
  • index.less:樣式表
  • index.vue:組件文件
  • calendar.vue:測試組件文件

1 - index.js 工具函數

// 定義月份英文縮寫
const englishMonthList = [
    'Jan',
    'Feb',
    'Mar',
    'Apr',
    'May',
    'Jun',
    'Jul',
    'Aug',
    'Sept',
    'Oct',
    'Nov',
    'Dec'
]

// 根據傳入日期獲取指定年月日時分秒
const getNewDate = (date) => {
    let year = date.getFullYear()
    let month = date.getMonth()
    let day = date.getDate()
    let hour = date.getHours()
    let minute = date.getMinutes()
    let second = date.getSeconds()
    return { year, month, day, hour, minute, second }
}

// 獲取指定年月日獲取日期
const getDate = (year, month, day) => {
    return new Date(year, month, day);
}


// 獲取當前月的英文拼寫
const englishMonth = (month) => {
    let engMonth = englishMonthList[month]
    return engMonth
}

// 導出函數
export {
    getNewDate,
    getDate,
    englishMonth
}
複製代碼

2 - index.vue 組件文件

<template>
    <div class='cai-calendar-wrapper' id='cai-calendar-wrapper'>
        <!-- 頭部,即未展開的組件 -->
        <div class='cai-calendar-header'>
            <div class='cai-calendar-header-time'>{{currentTime.hour}}:{{formatDate(currentTime.minute)}}:{{formatDate(currentTime.second)}}</div>
            <div class='cai-calendar-header-date'>
                {{currentTime.year}}年{{currentTime.month + 1}}月{{currentTime.day}}日
            </div>
        </div>
        <!-- 日曆容器  transition實現動畫效果-->
        <transition name='calendar'>
            <div class='cai-calendar-container' v-if='showCalendar'>
                <!-- 容器頭部,實現上下月切換,以及返回今天 -->
                <div class='container-header'>
                    <span class='container-header-date'>
                        {{choosenMonthDec}}
                    </span>
                    <i class='cai-icon-up' @click='handlePrevMonth'></i>
                    <i class='cai-icon-down' @click='handleNextMonth'></i>
                    <span class='container-header-today' @click='handleToday'>今天</span>
                </div>
                <!-- 列表頭行,顯示週一到週日 -->
                <div class="calendar-week">
                    <div v-for="(item, index) in calendarTitleArr" :key="index" class="week-item">{{item}}</div>
                </div>
                <!-- 列表 排列每個日期 -->
                <div class="calendar-week">
                    <div v-for="(item, index) in calendarList" :key="index" class="week-item date-item" :class='[{today:isCurrentDay(item.date)}]' @click='chooseDate(item)'>
                        <span>{{item.day}}</span>
                    </div>
                </div>
            </div>
        </transition>
        <!-- 自定義彈窗 transition實現動畫效果 -->
        <transition name='calendar-dialog'>
            <div class='cai-calendar-dialog' v-if='showCalendarDialog'>
                <i class='cai-icon-close' @click='showCalendarDialog = false'></i>
                <!-- 具名插槽實現用戶自定義內容 -->
                <slot name="content">
                </slot>
            </div>
        </transition>
    </div>
</template>

<script>
import * as utils from './index.js';
export default {
    name:'CaiCalendar',
    data(){
        return{
            currentTime:{ // 當前時間年月日,時分秒,用於頭部顯示實時時間
                year:'', 
                month:'', 
                day:'',
                hour:'',
                second:''
            },
            calendarTitleArr: [  // 星期英文拼寫
                'MON',
                'TUE',
                'WED',
                'THU',
                'FRI',
                'SAT',
                'SUN '
            ],
            calendarList: [], // 日曆數組
            choosenMonthDec:'', // 日曆容器頭部的可選月份描述
            choosenMonth:'', // 日曆容器頭部的可選月份
            interval:'', // 存儲定時器
            showCalendar:false, // 是否顯式完整日曆
            showCalendarDialog:false, // 是否顯示自定義彈窗
        }
    },
    mounted(){
        // 拖拽
        this.drag()
        // 第一次不延時
        this.handleNowDate()
        this.interval = setInterval( () => {
            this.handleNowDate()
        }, 1000)
    },
    methods:{
        // 拖拽函數
        drag(){
            var that = this
            var drag = document.getElementById('cai-calendar-wrapper')
            // //點擊某物體時,用drag對象便可,move和up是全局區域,
            // 也就是整個文檔通用,應該使用document對象而不是drag對象(不然,採用drag對象時物體只能往右方或下方移動)  
            drag.onmousedown = function(event){
                var event = event || window.event  //兼容IE瀏覽器
                // 鼠標點擊物體那一刻相對於物體左側邊框的距離=點擊時的位置相對於瀏覽器最左邊的距離-物體左邊框相對於瀏覽器最左邊的距離
                var diffX = event.clientX - drag.offsetLeft
                var diffY = event.clientY - drag.offsetTop
                var startX = event.clientX
                var startY = event.clientY
                if(typeof drag.setCapture !== 'undefined'){
                        drag.setCapture() 
                }
                // 鼠標移動,修改定位
                document.onmousemove = function(event){
                    var event = event || window.event
                    var moveX = event.clientX - diffX
                    var moveY = event.clientY - diffY
                    if(moveX < 0){
                        moveX = 0
                    }else if(moveX > window.innerWidth - drag.offsetWidth){
                        moveX = window.innerWidth - drag.offsetWidth
                        console.log(moveX)
                    }
                    if(moveY < 0){
                        moveY = 0
                    }else if(moveY > window.innerHeight - drag.offsetHeight){
                        moveY =  window.innerHeight - drag.offsetHeight
                    }
                    drag.style.left = moveX + 'px'
                    drag.style.top = moveY + 'px'
                }
                document.onmouseup = function(event){
                    var targetClass = event.target.className
                    var event = event || window.event
                    var endX = event.clientX
                    var endY = event.clientY
                    if(startX === endX && startY === endY){
                        if(targetClass === 'cai-calendar-header' || targetClass === 'cai-calendar-header-time' || targetClass === 'cai-calendar-header-date')
                        that.turnCalendar()
                    }
                    console.log(this)   // #document
                    this.onmousemove = null
                    this.onmouseup = null
                    // 適配低版本 ie
                    if(typeof drag.releaseCapture!='undefined'){  
                        drag.releaseCapture()  
                    } 
                }
            }
        },
        // 切換完整日曆
        turnCalendar(){
            this.visibleCalendar(this.currentTime.year,this.currentTime.month)
            this.showCalendar = !this.showCalendar
            this.showCalendarDialog = false
        },
        // 是不是今天 
        isCurrentDay (date) {
            let {year: currentYear, month: currentMonth, day: currentDay} = utils.getNewDate(new Date())
            let {year, month, day} = utils.getNewDate(date)
            return currentYear == year && currentMonth == month && currentDay == day
        },
        // 點擊上一個月
        handlePrevMonth () {
            let prevMonth = utils.getDate(this.choosenMonth.year,this.choosenMonth.month,1)
            prevMonth.setMonth(prevMonth.getMonth() - 1)
            this.choosenMonth.year = utils.getNewDate(prevMonth).year
            this.choosenMonth.month = utils.getNewDate(prevMonth).month
            this.visibleCalendar(this.choosenMonth.year,this.choosenMonth.month)
            this.$emit('handlePrevMonth',this.choosenMonth.year,this.choosenMonth.month) // 傳給父組件,上個年和月份做爲傳遞數據
        },
        // 點擊下一個月
        handleNextMonth () {
            let nextMonth = utils.getDate(this.choosenMonth.year,this.choosenMonth.month,1)
            nextMonth.setMonth(nextMonth.getMonth() + 1)
            this.choosenMonth.year = utils.getNewDate(nextMonth).year
            this.choosenMonth.month = utils.getNewDate(nextMonth).month
            this.visibleCalendar(this.choosenMonth.year,this.choosenMonth.month)
            this.$emit('handleNextMonth',this.choosenMonth.year,this.choosenMonth.month) // 傳給父組件,上個年和月份做爲傳遞數據
        },
        // 點擊回到今天
        handleToday () {
            this.choosenMonth.year = utils.getNewDate(new Date()).year
            this.choosenMonth.month = utils.getNewDate(new Date()).month
            this.visibleCalendar(this.choosenMonth.year,this.choosenMonth.month)
            this.$emit('handleToday',this.choosenMonth.year,this.choosenMonth.month) // 傳給父組件,上個年和月份做爲傳遞數據
        },
        // 點擊日期
        chooseDate(item){
            this.showCalendarDialog = true
            this.$emit('handleClickDay', item);
        },
        // 加載日曆
        visibleCalendar(YEAR,MONTH){
            let calendatArr = []

            // 獲取指定時間年月日
            let {year, month, day} = utils.getNewDate(utils.getDate(YEAR, MONTH, 1))
            
            // 獲取指定月第一天星期幾
            let currentFirstDay = utils.getDate(year, month, 1)
            let weekDay = currentFirstDay.getDay()
            console.log('weekDay',weekDay)
            
            // 計算開始時間
            let startTime = currentFirstDay - (weekDay - 1) * 24 * 60 * 60 * 1000
            console.log('startTime',startTime)

            // 獲取當前月份中日曆顯示幾天 ( 第一天是周5或周6,則顯示6行,不然顯示5行)
            let monthDayNum
            if (weekDay == 5 || weekDay == 6){
                monthDayNum = 42
            }else {
                monthDayNum = 35
            }

            // 從第一天開始計算,每進行一次循環,日期加一天
            for (let i = 0; i < monthDayNum; i++) {
                calendatArr.push({
                    date: new Date(startTime + i * 24 * 60 * 60 * 1000),
                    year: year,
                    month: month + 1,
                    day: new Date(startTime + i * 24 * 60 * 60 * 1000).getDate(),
                    clickDay: false,
                })
            }

            // 賦值日曆列表
            this.calendarList = calendatArr
            this.choosenMonth = {
                year:year,
                month:month
            }
            this.choosenMonthDec = `${utils.englishMonth(month)} ${year}`  // 日曆容器頭部的可選月份
        },
        // 獲取當前時間,並賦值currentTime
        handleNowDate(){
            let {year, month, day, hour, minute, second} = utils.getNewDate(new Date())
            this.currentTime = {year, month, day, hour, minute, second}
        },
        // 格式化日期 我的很多天期以 0X 的格式顯示
        formatDate(date){
            date = Number(date)
            return date < 10 ? `0${date}` : date
        }
    },
    destroyed(){
        // 清除定時器
        clearInterval('interval')
    }
}
</script>

<style lang="less" scoped>
@import './index.less';   // 加入樣式表
@import '../../CaiIcon/component/index.less';  // 圖標樣式表(可無論)
</style>
複製代碼

關於以上代碼,須要注意如下:數組

  • 獲取實時時間,經過設置Interval,在調用前,必須手動調用一次,避免第一次也延時致使效果不佳
  • 關於拖拽事件和點擊事件,二者會有衝突,因此選擇不添加點擊事件,在拖拽事件中判斷位置差,當位置未發生改變時,調用類點擊事件,展開日曆,不然實現拖拽效果

3 - index.less:樣式表

.cai-calendar-wrapper{
    width:540px;
    display: flex;
    flex-direction: column;
    align-items: center;
    position: fixed;
    user-select:none;
    .cai-calendar-header{
        width:100%;
        display: flex;
        flex-direction: column;
        justify-content:center;
        padding:20px;
        background:rgba(9,9,9,0.8);
        position:relative;
        &:before{
            content:'';
            position: absolute;
            top:0px;
            left:0px;
            height:100%;
            width:5px;
            background: #0097e6;
        }
        .cai-calendar-header-time{
            text-align: left;
            font-size:30px;
            font-weight: 500;
            color: #74b9ff;
            margin-left:20px;
            margin-bottom:10px;
        }
        .cai-calendar-header-date{
            text-align: left;
            font-size:15px;
            color: #fff;
            margin-left:30px;
        }
    }
    .cai-calendar-container{
        width:100%;
        margin-top:5px;
        opacity: 1;
        padding:20px;
        background:rgba(9,9,9,0.8);
        transform-origin:top;
        transform:rotateX(0deg);
        display:flex;
        flex-direction: column;
        .container-header{
            display: flex;
            justify-content: flex-end;
            align-items: center;
            color:#fff;
            margin-bottom:10px;
            .container-header-date{
                margin-right:10px;
            }
            .container-header-today{
                margin:0px 10px;
                padding:5px 10px;
                color: #74b9ff;
                font-size:12px;
                border:1px solid #74b9ff;
                border-radius:5px;
                cursor:pointer;
                transition: all .2s linear;
            }
            .container-header-today:hover{
                color:#0984e3;
                border-color:#0984e3;
            }
            .cai-icon-up {
                margin-right:10px;
                cursor:pointer;
                transition:all .2s linear;
            }
            .cai-icon-down {
                cursor:pointer;
                transition:all .2s linear;
            }
            .cai-icon-up:hover {
                color:#0984e3;
            }
            .cai-icon-down:hover {
                color:#0984e3;
            }
        }
        .calendar-week{
            width: 100%;
            display: flex;
            flex-wrap: wrap;
            border-right: none;
            list-style: none;
            margin: 0;
            padding: 0;
            .week-item{
                width: 57px;
                text-align: center;
                font-size: 16px;
                color: #fff;
                font-weight: 600;
                padding:0px;
                margin:10px;
            }
            .date-item{
                cursor:pointer;
                transition:all .2s linear;
                position: relative;
            }
            .date-item:hover{
                color:#0984e3;
            }
            .today{
                color:#74b9ff;
            }
        }
    }
    .cai-calendar-dialog{
        position: absolute;
        top:0px;
        bottom:0px;
        left:-330px;
        width:300px;
        background:rgba(9,9,9,0.8);
        color:#fff;
        overflow: auto;
        .cai-icon-close{
            position: absolute;
            top:5px;
            right:5px;
            color:#fff;
            cursor: pointer;
            transition: all .2s linear;
        }
        .cai-icon-close:hover{
            color:#0097e6;
        }
    }
    
    // 關於彈窗的滾動條樣式覆蓋
    .cai-calendar-dialog::-webkit-scrollbar {
        width: 5px;
        margin:2px;
        height: 5px;
    }
    .cai-calendar-dialog::-webkit-scrollbar-button {
        display: none;
    }
    .cai-calendar-dialog::-webkit-scrollbar-track {
        background-color: transparent;
    }
    .cai-calendar-dialog::-webkit-scrollbar-thumb {
        width: 3px;
        background-color: #0097e6;
        -webkit-border-radius: 3em;
        -moz-border-radius: 3em;
        border-radius: 3em;
    }
    
    // 日曆 - 進入/離開 動畫
    .calendar-enter-active, .calendar-leave-active {
        transition: all 1s
    }
    .calendar-enter, .calendar-leave-active {
        transform:rotateX(-90deg);
        opacity:0;
    }
    // 日曆彈窗 - 進入/離開 動畫
    .calendar-dialog-enter-active, .calendar-dialog-leave-active {
        transition: all 1s
    }
    .calendar-dialog-enter, .calendar-dialog-leave-active {
        transform:translateX(30px);
        opacity:0;
    }
}
複製代碼

4 - calendar.vue:測試組件文件


尾聲

開源地址瀏覽器

github.com/Jason9708/C…bash

該開源git是一個開源UI庫項目,目前開發近20款適配Vue的UI組件,有興趣的小夥伴給點⭐app

部分組件截圖less

相關文章
相關標籤/搜索