react-calendar
前言
一个可以鼠标按下上下拖动,抬起收起/展开
显示的日历;在拖动途中当前周一直可展示利用css粘性定位position: sticky;
实现;
可通过div定位关系设置sticky
模拟top
,height
的调整观察这里不展开介绍;
通过给选中行设置粘性定位,调整该元素所在的父元素定位top
还用到了css中的var()
--*(自定义属性、变量)
,具体实现就是通过调整css变量
实现拖拽交互;现已上传npm可下载尝试:https://www.npmjs.com/package/sa-calendar-react
用到的库\框架…
vite
https://cn.vitejs.dev/react
https://zh-hans.react.dev/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
,--height
随styles.open
和styles.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
事件触发早于mousedown
和mouseup
;就会在一些场景出现抽出例如:
按下格子想滑动,可是需求有点击更新日期的需求这就冲突了;
这里采用通过变量moveNum
滑动数值变化判断是否是滑动,我们按下后不移动是不会触发mousemove
每次移动moveNum+1
;在mouseup
中判断moveNum>5 表示滑动 <5则这里就表示点击 触发props.onClick
重要的地方就在以上地方,可gitee看完整代码 https://gitee.com/yin-chunyang/react-calendar/tree/calendar-toggle/