react一个收起周/展开月日历

react-calendar

前言

一个可以鼠标按下上下拖动,抬起收起/展开显示的日历;在拖动途中当前周一直可展示利用css粘性定位position: sticky;实现;

可通过div定位关系设置sticky模拟top,height的调整观察这里不展开介绍;

通过给选中行设置粘性定位,调整该元素所在的父元素定位top

还用到了css中的var() --*(自定义属性、变量),具体实现就是通过调整css变量实现拖拽交互;现已上传npm可下载尝试:https://www.npmjs.com/package/sa-calendar-react

用到的库\框架…

  1. vite https://cn.vitejs.dev/
  2. react https://zh-hans.react.dev/
  3. typescript https://www.typescriptlang.org/

vue3版本的

这个比较早了当时比较菜可下载源码修改
https://www.npmjs.com/package/sa-calendar-vue3

日历的构成

1. 一个月开始于星期几

假设当月第一天周2,就表示要展示上个月的最后2天!!!

//获取当月第一天周几方式
let date = new Date();
const year = date.getFullYear();
const month = date.getMonth();
const day = date.getDate();
// 年,月,日
const firstDayOfMonth = new Date(year, month, 1).getDay(); // 一个月开始于星期几

2.当月的天数

当月天数就一个点,每年的2月份随着是否闰年变化

export const getDays = (year: number): number[] => {
    let leapYearOfDay: number = 0;
    if (((year % 4) == 0) && ((year % 100) != 0) || ((year % 400) == 0)) {
        leapYearOfDay = 1;
    } else {
        leapYearOfDay = 0;
    }
    return [31, 28 + leapYearOfDay, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
}

3.剩余天数

日历有67 57的,通过减去1和2的长度得到剩余的格子数量从1开始即可

在这里插入图片描述

目录介绍

    .
    ├── calendar
    │   ├── composition
    │   │   ├── dateListMethod.ts
    │   │   ├── dateToTable.ts
    │   ├── styles
    │   │   ├── calendar.module.css
    │   ├── tools
    │   │   ├── deepClone.ts
    │   ├── types
    │   │   ├── calendar.props.ts
    │   │   ├── date.table.ts
    │   │   ├── index.ts
    │   ├── calendar.row.tsx
    │   ├── calendar.tsx
    │   ├── calendar.week.tsx
  • calendar-composition
    • dateListMethod.ts 用来处理用于时间相关数组的方法,包含 一年12月每月天数函数 随不同展示第一天周几处理显示周的函数
    • dateToTable.ts 通过传入日期返回上月,本月,下月相连接的数组
  • styles
    • calendar.module.css 组件的样式
  • tools
    • deepClone.ts 用来克隆对象的函数
  • types
    • calendar.props.ts props所需参数类型
    • date.table.ts 渲染日期数组的类型
    • index.ts 入口文件暴露出types下文件的类型

注意地方

可gitee看完整代码 https://gitee.com/yin-chunyang/react-calendar/tree/calendar-toggle/
有点小多展开讲都是琐碎片段,总结一下注意的地方

css变量处理

const setStyleCellHeight = (): Record<string, any> => {
    let cellH: string | null;
    if ('open' in props) {
        if (props.cellHeight) cellH = props.cellHeight + 'px';
        else cellH = defaultProps.cellHeight + 'px';
    } else {
        cellH = null;
    }

    let tableH: string | null;
    let hideTableH: string | null;
    if (cellH) {
        tableH = (parseFloat(cellH) * DayList.length) + 'px';
        hideTableH = -parseFloat(cellH) * (DayList.length - 1) + 'px';
    } else {
        tableH = null;
        hideTableH = null;
    }

    return {
        '--cellHeight': cellH,
        '--tableHeight': tableH,
        '--hideTableHeight': hideTableH,
        '--rows-len': DayList.length
    }
}

是否开启收缩

通过是否包含props.open判断是否开启收缩

当含有open属性设置className:styles.toggle

开启拖拽功能 需要设置固定高度,和有粘性定位的父类定位;调整选中行的样式

.toggle{
    height: var(--height);
}
.toggle .calendar-table{
    position: absolute;
    top: var(--top);
}
.toggle .calendar-row-open .calendar-row-cell-rect {
    outline-color: #1677ff;
}

open:true

表示展开显示月份日期,设置className styles.open

open:false

表示展开显示月份日期,设置className styles.close

在滑动途中清除掉过度样式

通过变量clearTransition判断,每次滑动修改值

通过该值设置className去掉styles.open \ styles.close

.open {
    --top:0 !important;
    --height:var(--tableHeight) !important;
    transition:var(--transition);
}

.close {
    --tableHeight: var(--cellHeight) !important;
    --top: var(--hideTableHeight) !important;
    transition:var(--transition);
}

.open .calendar-table,
.close .calendar-table{
    transition:var(--transition);
    top: var(--top);
}

滑动时移动值的修改

在滑动时通过修改css变量--top,--height达到移动距离的效果

const mouseMove = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    if (!isDown) return;
    setMoveNum(moveNum + 1);
    if (moveNum > 5) {
        setClearTransition(true);
        if (!props.cellHeight) return;
        if (!calendarTable.current) return;
        if (!calendarLayer.current) return;

        let offsetY = event.pageY - mouseEvent.startY;
        let dif = event.pageY - mouseEvent.startY;

        if(open){
            // 滑动中如果已经到最顶部就让toggle(visible)为true收起状态
            if (offsetY < -(tableHeight - props.cellHeight)) {
                offsetY = -(tableHeight - props.cellHeight);
                setOpen(false)
            }
            if (dif > 0) {
                offsetY = 0;
            }
        }else{
            if (dif < 0) {
                offsetY = 0;
            }
            offsetY = -(tableHeight - props.cellHeight - offsetY);
            let calendarLayerHeight = (tableHeight + offsetY)
            if (calendarLayerHeight > tableHeight) {
                calendarLayerHeight = tableHeight;
                setOpen(true)
            }
            calendarLayer.current.style.setProperty('--height', calendarLayerHeight + 'px');
        }

        let minY = -tableHeight;
        let maxY = 0;
        offsetY = Math.max(minY, Math.min(offsetY, maxY));

        mouseEvent.moveY = offsetY;
        setMouseEvent(deepClone(mouseEvent));

        calendarLayer.current.style.setProperty('--top', offsetY + 'px');
        calendarLayer.current.style.setProperty('--height', tableHeight + offsetY + 'px');
    }
}

滑动结束鼠标抬起事件恢复到初始

在移动抬起后恢复--top,--heightstyles.openstyles.close一直

const mouseUp = (_:any, record: any) => {
    setIsDown(false);
    mouseEvent.endY = _.clientY;
    mouseEvent.moveY = 0;
    setMouseEvent(deepClone(mouseEvent));
    if (moveNum < 5) {
        props.onClick && props.onClick(record);
    } else {
        if(!props.cellHeight) return;
        if(!calendarTable.current) return;
        if(!calendarLayer.current) return;
        let dif = mouseEvent.startY - mouseEvent.endY;
        if(dif > props.cellHeight){
            setOpen(false);
            calendarLayer.current.style.setProperty('--top', 'var(--hideTableHeight)');
            calendarLayer.current.style.setProperty('--height','var(--cellHeight)' );
        }else{
            setOpen(true);
            calendarLayer.current.style.setProperty('--top', '0');
            calendarLayer.current.style.setProperty('--height', 'var(--tableHeight)');
        }
        if(open != null){
            props.onToggle && props.onToggle(open);
        }
    }
    setClearTransition(false);
}

!!点击事件如何实现的

因为这个click事件触发早于mousedownmouseup;就会在一些场景出现抽出例如:

按下格子想滑动,可是需求有点击更新日期的需求这就冲突了;

这里采用通过变量moveNum滑动数值变化判断是否是滑动,我们按下后不移动是不会触发mousemove每次移动moveNum+1;在mouseup中判断moveNum>5 表示滑动 <5则这里就表示点击 触发props.onClick


重要的地方就在以上地方,可gitee看完整代码 https://gitee.com/yin-chunyang/react-calendar/tree/calendar-toggle/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值