preface
Recent company want to have a demand to achieve nailing calendar effect, used nailing calendar should all know the above can switch this week and month calendar calendar calendar click below the corresponding schedule when rolling, then rolling schedule when the calendar will be placed at the top of the agenda is ultimately selected above such a linkage effect, because the schedule list have access to the company’s business code, So this may only be the calendar component of the implementation process, is probably the following way to make a GIF for you to see 🌺
The preparatory work
NPM I-D DayJS package is used to process calendar rendering logic
Arrange a throttling function to be used later
const throttle = (fun: (val: any) => void, time: number) = > {
let delay = 0
return (. params: any) = > {
const now = +new Date(a)if (now - delay > time) {
fun.apply(this, params)
delay = now
}
}
}
Copy the code
Since we are using TSX, we will paste all the types up in a moment and then use them directly
type weekType = {
id: number.name: string
}
type toutchType = { x: number.y: number }
type dayjsType = Array<dayjs.Dayjs>
type generateWeekDataType = {
currenWeekFirstDay: dayjs.Dayjs,
generateWeekDateList: Array<dayjsType>
}
type generateMonthDataType = {
currentMonthFirstDay: dayjs.Dayjs,
generateMonthDateList: Array<dayjsType>
}
Copy the code
GenerateMonthData is used to generate the monthly calendar and generateWeekData is used to generate the weekly calendar
As an aside to the calendar generation logic, dayjsDate is used to get the current month, followed by startOf to get the first day of the first week
Because we need to render all the dates of a month including the dates of the previous month displayed in the current month and the dates of the next month displayed in the current month
GenerateMonthData returns three arrays each time to indicate that the current month has passed from the previous month to the next month and we render only the current month, Each time the calendar is then swiped, it will continue to generate the calendar of the current month and the week of the previous and next month based on the current month
generateMonthData
/ * * * *@param {*} DayjsDate DayJS object */
const generateMonthData = (dayjsDate: dayjs.Dayjs): generateMonthDataType= > {
Return the first day of the current month
const currentMonthFirstDay = dayjsDate.startOf('month')
// Returns the first day of the first week of the month
const currentMonthStartDay = currentMonthFirstDay.startOf('week')
// Last month
const prevMonthFirstDay = currentMonthFirstDay.subtract(1.'month')
const prevMonthStartDay = prevMonthFirstDay.startOf('week')
// Next month
const nextMonthFirstDay = currentMonthFirstDay.add(1.'month')
const nextMonthStartDay = nextMonthFirstDay.startOf('week')
return {
currentMonthFirstDay,
generateMonthDateList: [
new Array(42).fill(' ').map((_, index) = > prevMonthStartDay.add(index, 'day')),
new Array(42).fill(' ').map((_, index) = > currentMonthStartDay.add(index, 'day')),
new Array(42).fill(' ').map((_, index) = > nextMonthStartDay.add(index, 'day'))],}}Copy the code
generateWeekData
/ * * * *@param {*} DayjsDate DayJS object */
const generateWeekData = (dayjsDate: dayjs.Dayjs): generateWeekDataType= > {
const currenWeekStartDay = dayjsDate.startOf('week')
const prevWeekStartDay = currenWeekStartDay.subtract(1.'week')
const nextWeekStartDay = currenWeekStartDay.add(1.'week')
return {
currenWeekFirstDay: currenWeekStartDay,
generateWeekDateList: [
new Array(7).fill(' ').map((_, index) = > prevWeekStartDay.add(index, 'day')),
new Array(7).fill(' ').map((_, index) = > currenWeekStartDay.add(index, 'day')),
new Array(7).fill(' ').map((_, index) = > nextWeekStartDay.add(index, 'day'))],}}Copy the code
To the chase
Because of the layout of our business, I have divided it into two parts, so far only the calendar component is left
const ScheduleList = () = > {
return <>
<div className='schedule_view_wrap'>
<div className='schedule_list_section'>{/* Calendar component */}<CalendarComp />
</div>
</div>
</>
}
export default ScheduleList
Copy the code
Look at the
concrete logic. The following declarations and logic code are all in this component, and most functions and variables are commented out
CalendarComp components
const CalendarComp = () => {
}
Copy the code
useRef
const { current } = useRef({
currentDate: dayjs().format('YYYY-MM-DD'), // Get today
isTouch: false.// Control the sliding animation
touchStartX: 0.// Determine the slide position
touchStartY: 0.calendarRef: { offsetWidth: 0 } // Determine the slide position
})
Copy the code
useState
const dayjsDate = dayjs(current.currentDate) // Convert the date of the day into a dayJS object ready to get the rendered monthly calendar
const { currentMonthFirstDay, generateMonthDateList = [] } = generateMonthData(dayjsDate)
const { currenWeekFirstDay, generateWeekDateList = [] } = generateWeekData(dayjsDate)
const [mounthFirstDay, setMounthFirstDay] = useState<dayjs.Dayjs>(currentMonthFirstDay) // The first day of the month
const [mountDateList, setMountDateList] = useState<Array<dayjsType>>(generateMonthDateList)// The month calendar needs to display dates including the previous month and the next month
const [weekFirstDay, setWeekFirstDay] = useState<dayjs.Dayjs>(currenWeekFirstDay)
const [weekDateList, setWeekDateList] = useState<Array<dayjsType>>(generateWeekDateList)// The week calendar needs to display dates including the previous week, the current week and the next week
const [selectDate, setSelectDate] = useState<string>(current.currentDate) // Set the style of the selected day
const [moveIndex, setMoveIndex] = useState<number> (0) // Record the position of the calendar slide
const [touch, setTouch] = useState<toutchType>({ x: 0.y: 0 }) // The position of the finger slide
const [isMountView, setIsMountView] = useState<boolean> (false) // True/weekly calendar false/ monthly calendar
const [weekInd, selectWeekInd] = useState<number> (0) // Record the selected index of the weekly calendar
Copy the code
weekList
const weekList: Array<weekType> = [{
id: 0.name: 'day'
}, {
id: 1.name: '一'
}, {
id: 2.name: '二'
}, {
id: 3.name: '三'
}, {
id: 4.name: 'four'
}, {
id: 5.name: 'five'
}, {
id: 6.name: '六'
}]
Copy the code
Core business structure
return <div className='calendar_comp'>{/* display current month title */}<header>
<span>{handelFormtDate(isMountView ? WeekFirstDay: mounthFirstDay, 'YYYY ')}</span>
</header>{/* Week list */}<p className='week_list'>
{
weekList.map(item => {
return <span key={item.id}>{item.name}</span>})}</p>{/* Monthly calendar view/weekly calendar view */}<div className={`calendar_comp_wrapThe ${isMountView ? 'pushHei' : 'pullHei` '}}ref={(e: any) = > current.calendarRef = e}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div className='calendar_comp_section' style={{
transform: `translateX(${-moveIndex * 100} %) `,}} >
{
(isMountView ? weekDateList : mountDateList).map((item, index) => {
return <ul key={index} className='calendar_list_day'
style={{
transform: `translateX(${(index - 1 + moveIndex + (current.isTouch ? touch.x : 0)) * 100`} %),transitionDuration:` ${current.isTouch ? 0 : 0.3}s`}} >
{
item.map((date, ind) => {
const isOtherMonthDay = isMountView || date.isSame(mounthFirstDay, 'month')
const formtDate = handelFormtDate(date, 'YYYY-MM-DD')
return <li
key={ind}
onClick={()= > {
handelSelectDate(formtDate, isOtherMonthDay, ind)
}}
style={{ color: isOtherMonthDay ? '#333333' : '#CCCCCC' }}
>
<span className={renderClassName(formtDate)}>{date.format('DD')}</span>
</li>})}</ul>})}</div>
</div>{/* Calendar bottom operation close/expand button */}<span className='calendar_pull' onClick={()= >{ handelIsMountView() }}>{isMountView ? 'expand' : 'fold '}</span>
<p className='current_day'>{handelFormtDate(selectDate, 'MM month DD day ')} {' week ${getWeekDate(selectDate)? .name}`}</p>
</div >
Copy the code
The core business logic is presented in sections (all of which may look tiresome) and other functions are gradually introduced starting with useEffect
The first paragraph starts with useEffect
useEffect(() = > {
handelSetWeekIndex(current.currentDate, weekDateList) // Set the selected position of the weekly calendar
setSelectDate(current.currentDate) // Initialize the calendar on the selected day
}, [])
useEffect(() = > {
// The first day of the next month is directly selected when the calendar switch is triggered
if(! isMountView) {/ / month calendar
let currentM = handelFormtDate(mounthFirstDay, 'YYYY-MM-DD')
setSelectDate(currentM)
}
}, [mounthFirstDay])
// Format the date function
const handelFormtDate = (date: dayjs.Dayjs | string.exp: string) :string= > {
return dayjs(date).format(exp)
}
// Set the selected position of the weekly calendar
const handelSetWeekIndex = (selectDate: string | dayjs.Dayjs, generateWeekDateList: Array<{ [key: string] :any} >) :void= > {
const currentWeekDateList = generateWeekDateList[1]
let weekIndex = 0
// Set the location of the weekly calendar to be selected (for example, if it is currently on Wednesday when switching to weekly calendar, make sure it is still on Wednesday when switching to weekly calendar)
currentWeekDateList.forEach((v: any, index: number) = > {
if (handelFormtDate(v, 'YYYY-MM-DD') === selectDate) weekIndex = index
})
selectWeekInd(weekIndex)
}
// Weekly title list render logic
const getWeekDate = (cDate: string) :weekType= > {
const day = new Date(cDate).getDay()
return weekList[day];
}
Copy the code
The second paragraph starts with handleTouchStart
// Slide calendar to trigger
const handleTouchStart = (e: any) = > {
e.stopPropagation()
const touchs = e.touches[0]
current.touchStartX = touchs.clientX
current.touchStartY = touchs.clientY
current.isTouch = true
}
// Slide calendar trigger
const handleTouchMove = throttle((e: any) = > {
e.stopPropagation()
const touchs = e.touches[0]
const moveX = touchs.clientX - current.touchStartX
const moveY = touchs.clientY - current.touchStartY
const calendarWidth = current.calendarRef.offsetWidth
if (Math.abs(moveX) > Math.abs(moveY)) {
// Slide left and right
setTouch({ x: moveX / calendarWidth, y: 0})}},25)
// Slide calendar end trigger
const handleTouchEnd = (e: any) = > {
e.stopPropagation()
current.isTouch = false
const touchX = Math.abs(touch.x)
const touchY = Math.abs(touch.y)
const newTranslateIndex = touch.x > 0 ? moveIndex + 1 : moveIndex - 1
if (touchX > touchY && touchX > 0.15) {
if (isMountView) {
// Weekly calendar goes logic
const nextWeekFirstDay = weekFirstDay[touch.x > 0 ? 'subtract' : 'add'] (1.'week')
const { currenWeekFirstDay = null, generateWeekDateList = [] } = generateWeekData(nextWeekFirstDay)
// Update the weekly calendar view
updateWeekView(newTranslateIndex, currenWeekFirstDay, generateWeekDateList)
// Weekly calendar scrolling is selected by default
const currentWeekDays = generateWeekDateList[1]
const selectWeekDay = currentWeekDays[weekInd]
const formtSelectWeekDay = handelFormtDate(selectWeekDay, 'YYYY-MM-DD')
setSelectDate(formtSelectWeekDay)
} else {
// Monthly calendar goes logic
const nextMonthFirstDay = mounthFirstDay[touch.x > 0 ? 'subtract' : 'add'] (1.'month')
const { currentMonthFirstDay = null, generateMonthDateList = [] } = generateMonthData(nextMonthFirstDay)
// Update the monthly calendar view
updateMounthView(newTranslateIndex, currentMonthFirstDay, generateMonthDateList)
}
}
setTouch({ x: 0.y: 0})}// Update the weekly calendar view
const updateWeekView = (index: number.currentFirstDay: any.dateList: Array<dayjsType>): void= > {
setMoveIndex(index)
setWeekFirstDay(currentFirstDay)
setWeekDateList(dateList)
}
// Update the monthly calendar view
const updateMounthView = (index: number.currentFirstDay: any.dateList: Array<dayjsType>): void= > {
setMoveIndex(index)
setMounthFirstDay(currentFirstDay)
setMountDateList(dateList)
}
Copy the code
The third paragraph starts with the handelSelectDate calendar selection logic
// Calendar select logic
/ * *@isOtherMonthDay Check whether the current day is in the current month. If it is not in the current month, the click will automatically switch to next month or last month **/
const handelSelectDate = (formtDate: string, isOtherMonthDay: boolean, index: number) = > {
let selectM = handelFormtDate(formtDate, 'YYYYMM')
let currentM = handelFormtDate(mounthFirstDay, 'YYYYMM')
if(! isOtherMonthDay) {// Click last month or next month in the current month
const { generateMonthDateList = [] } = generateMonthData(dayjs(formtDate))
const newTranslateIndex = selectM < currentM ? moveIndex + 1 : moveIndex - 1
// Update the monthly view
updateMounthView(newTranslateIndex, dayjs(formtDate), generateMonthDateList)
}
// Ensure that the selected position in the calendar is correct when expanding and collapsing
if (isMountView) {
// Weekly calendar records the selected position
selectWeekInd(index)
}
// Update the calendar selection style
setSelectDate(formtDate)
}
// Render the selected styles. This is written as a function to distinguish the styles of the day from other selected styles
const renderClassName = (formtDate: string) :string= > {
if (selectDate === formtDate) return 'selectDay'
if (formtDate === current.currentDate) return 'currentDay'
return ' '
}
Copy the code
Start with handelIsMountView by switching the weekly/monthly calendar
// Monthly calendar/weekly calendar switch
const handelIsMountView = () = > {
// Generate the corresponding week calendar and month calendar based on the currently selected day
const dayjsDate = dayjs(selectDate)
const{ generateWeekDateList = [], generateMonthDateList = [] } = { ... generateMonthData(dayjsDate), ... generateWeekData(dayjsDate) }constflag = ! isMountViewif (flag) {
// Update the weekly calendar
setWeekFirstDay(dayjsDate)
setWeekDateList(generateWeekDateList)
// When updating the calendar, you need to record the current position
handelSetWeekIndex(selectDate, generateWeekDateList)
} else {
// Update the monthly calendar
setMounthFirstDay(dayjsDate)
setMountDateList(generateMonthDateList)
}
setIsMountView(flag)
}
Copy the code
This is the main logical and business structure of the calendar component
Also paste the style
.schedule_view_wrap {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
.schedule_list_section{
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column; }}.calendar_comp {
width: 100%;
background: #fff;
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 37px 40px 23px 40px;
background: #fff;
span {
font-size: 36px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: # 000000;
}
img {
width: 40px;
height: 40px; }}.week_list {
display: flex;
span {
width: calc(100%/7);
height: 40px;
text-align: center;
line-height: 40px;
font-size: 24px;
font-family: PingFangSC-Regular, PingFang SC;
color: # 999999; }}.calendar_comp_wrap {
width: 100%;
position: relative;
&.pullHei {
height: 480px;
transition: 0.25 s;
}
&.pushHei {
height: 80px;
transition: 0.15 s;
}
.calendar_comp_section {
width: 100%;
display: flex;
.calendar_list_day {
display: flex;
flex-wrap: wrap;
padding-top: 8px;
flex-shrink: 0;
width: 100%;
position: absolute;
li {
margin-bottom: 32px;
width: calc(100%/7);
height: 48px;
text-align: center;
line-height: 48px;
font-size: 28px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: # 333333;
.currentDay {
background: rgba(255.142.34.0.1) ! important;
color: #FF8E22 ! important;
padding: 6px 10px;
border-radius: 8px;
}
.selectDay {
border-radius: 8px;
background: #FF8E22 ! important;
color: #FFFFFF ! important;
padding: 6px 10px;
&.selectDayActive{
padding: 6px 18px; }}.circle {
display: block;
width: 8px;
height: 8px;
background: #ccc;
border-radius: 50%;
margin: 0 auto;
margin-top: 10px;
}
}
}
}
}
.calendar_pull {
width: 48px;
height: 8px;
padding-bottom: 22px;
padding-top: 10px;
margin: 0 auto;
display: block;
}
.current_day {
width: 100%;
text-align: center;
padding-bottom: 10px;
font-size: 28px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: # 333333; }}Copy the code
The last
As mentioned above, because the schedule list has been connected to the company’s business, it is not convenient to post it. However, if you really need it, you can discuss it in the comment section and attach the git address: github.com/gaoxinxiao/…