基于React实现无限滚动的日历详细教程,附源码【手写日历教程第二篇】
前言
最常见的日历大部分都是滚动去加载更多的月份,而不是让用户手动点击按钮切换日历月份。滚动加载的交互方式对于用户而言是更加丝滑和舒适的,没有明显的操作割裂感。
那么现在需要做一个这样的无限滚动的日历,前端开发者应该如何去思考和代码实现呢?下面我会详细的介绍实现思路和步骤。
实现步骤
渲染单个月日历
如何对于如何实现单个月份的日历没有思路的同学需要先阅读一下这篇简单日历的实现教程https://blog.csdn.net/m0_37890289/article/details/132457676;通过阅读这篇教程可以实现单月的渲染。
尝试同时渲染多个月份
对于无限滚动的日历,其实就是把所有的月份都放在一个列表中,然后用户滚动这个列表就行了,也就是我们想要的丝滑的切换日历。但是实现的话肯定不能真的把所有的月份都提前准备好,而且谁能知道未来有多少月。
所以这里可以利用滚动加载的方式进行月份数据的加载和列表数据的组装,关于滚动加载,我之前也写过一篇教程,没有思路的同学可以参考一下。https://blog.csdn.net/m0_37890289/article/details/130774836
好,那么好,接下来我们就开始动手实现这个无限滚动的日历。首先我们先尝试渲染3个月,当前月,当前前一个月,当前后一个月。
import "./index.css";
import { getDaysOfMonth } from "../../utils";
import { useCallback, useMemo, useState } from "react";
import dayjs, { Dayjs } from "dayjs";
import "dayjs/locale/zh-cn";
dayjs.locale("zh-cn");function ScrollCalendar() {// 获取月的所有日期const getDays = useCallback((month: Dayjs) => {return getDaysOfMonth(month.year(), month.month() + 1);}, []);// 定义好整个列表const [schedules, setSchedules] = useState(() => {return [dayjs().subtract(1, "month"), dayjs(), dayjs().add(1, "month")].map(month => ({month,days: getDays(month),}),);});const weekTitles = useMemo(() => {return [...Array(7)].map((_, weekInx) => {return dayjs().day(weekInx);});}, []);return (<div className="App"><div className="calendar"><div className="calendar-title">{weekTitles.map(title => {return <div className="calendar-week">{title.format("dd")}</div>;})}</div>{schedules.map(schedule => {return (<div><div className="calendar-month"><div>{schedule.month.format("MMM YYYY")}</div></div><div className="calendar-content">{schedule.days.map(day => {return <div className="calendar-day">{day.format("DD")}</div>;})}</div></div>);})}</div></div>);
}export default ScrollCalendar;

虽然多个月份的日历列表是渲染出来,但是看起来很奇怪,由于我们之前为了让单个月份的数据看起来完成,我们将上个月和下个月的数据填充到当前月,让日历看起来比较完整。
但是如果同时渲染多个月份,还将相邻月份的日期拿来填充,这肯定是不合理的。那么我们应该数据源头进行调整。接下来我们调整一下获取某一个月内所有日期的工具方法getDaysOfMonth。
-
关键步骤
非当前月就填充null,要把位置占住,不然又要出现每个月的1号都是在第一列的情况。
import dayjs from "dayjs";export const getDaysOfMonth = (year: number, month: number) => {let firstDayOfMonth = dayjs(`${year}-${month}-1`);let lastDayOfMonth = dayjs(`${year}-${month + 1}-1`).subtract(1, "day");// 开始补全第一天前的日期while (firstDayOfMonth.day() !== 0) {firstDayOfMonth = firstDayOfMonth.subtract(1, "day");}// 开始补全最后一天后的日期while (lastDayOfMonth.day() !== 6) {lastDayOfMonth = lastDayOfMonth.add(1, "day");}const days = [];const currentMonth = dayjs(`${year}-${month}-1`);let tempDate = firstDayOfMonth;while (tempDate.isBefore(lastDayOfMonth) || tempDate.isSame(lastDayOfMonth)) { // 关键步骤,非当前月就填充null,要把位置占住,不然又要出现每个月的1号都是在第一列的情况**if (tempDate.isSame(currentMonth, "month")) {days.push(tempDate);} else {days.push(null);}**tempDate = tempDate.add(1, "day");}return days;
};

让日历列表滚动起来
上面我们已经成功将3个月份成功渲染出来了,接下来我们通过列表滚动加载的思路,分别监听列表滚动置顶和滚动触底这两个时机。
const calendarRef = useRef<HTMLDivElement>(null);useEffect(() => {let prevScrollHeight = 0;const scrollEvent = debounce(async e => {let scrollHeight = e.target.scrollHeight;let scrollTop = e.target.scrollTop;let offsetHeight = e.target.offsetHeight;if (scrollTop < 100) {console.log("列表置顶");setSchedules(schedules => {const lastSchedule = schedules[0];const prevMonth = lastSchedule.month.subtract(1, "month");const prevSchedule = {month: prevMonth,days: getDays(prevMonth),};return [prevSchedule, ...schedules];});const targetScrollTop =scrollTop + (scrollHeight - prevScrollHeight) + 50;calendarRef.current?.scrollTo({ top: targetScrollTop });// 记录前一个滚动高度prevScrollHeight = scrollHeight;}if (offsetHeight + scrollTop >= scrollHeight - 10) {console.log("列表触底,触发接口请求数据");setSchedules(schedules => {const lastSchedule = schedules[schedules.length - 1];const nextMonth = lastSchedule.month.add(1, "month");const nextSchedule = {month: nextMonth,days: getDays(nextMonth),};return [...schedules, nextSchedule];});}}, 100);calendarRef.current?.addEventListener("scroll", scrollEvent);return () => {if (calendarRef.current) {}};}, []);<divclassName="calendar"style={{height: "100vh",overflowY: "scroll",}}ref={calendarRef}>...
</div>

总结
本文实现了无限滚动的日历组件,满足了很大一部分需求的基础,有需要的同学可基于源码进行二次修改。有任何问题都可以进行留言,如何文章对你有帮忙,可以帮我点个赞🦉。源码链接https://github.com/levenx/react-calendar-training/tree/main/src/pages/scroll-calendar
感兴趣的可访问DEMO页面https://calendar.levenx.com/#/scroll-calendar
相关文章:
基于React实现无限滚动的日历详细教程,附源码【手写日历教程第二篇】
前言 最常见的日历大部分都是滚动去加载更多的月份,而不是让用户手动点击按钮切换日历月份。滚动加载的交互方式对于用户而言是更加丝滑和舒适的,没有明显的操作割裂感。 那么现在需要做一个这样的无限滚动的日历,前端开发者应该如何去思考…...
68、使用aws官方的demo和配置aws服务,进行视频流上传播放
基本思想:参考官方视频,进行了配置aws,测试了视频推流,rtsp和mp4格式的视频貌似有问题,待调研和解决 第一步:1) 进入aws的网站,然后进入ioT Core 2)先配置 Thing types & Thing,选择香港的节点,然后AWS ioT--->Manage---> Thing type 然后输入名字,创建Th…...
数据库
表 记录:行 字段(属性): 列 以行列的形式就组成了表(数据存储在表中) 关系数据库的表由记录组成,记录由字段组成,字段由字符或数字组成。它可以供各种用户共享, 具有最小冗余度和较高…...
深入了解fcntl函数:Linux系统编程中的文件控制
文章目录 概述介绍函数原型与参数 拓展:fcntl改文件属性总结 概述 摘要: fcntl函数是Linux系统编程中一个重要的函数,用于对文件描述符进行各种控制操作。本文将详细介绍fcntl函数的原型、各个参数的用法,以及阻塞和非阻塞模式切换的方法&am…...
汇川技术内推码
[庆祝]不一样的内推码[庆祝]:IVSM2R 投递了可以评论下名字,我会帮忙留意进度。 汇尔成川,共赴星海,欢迎加入,职等你来。 嵌入式软硬件,机器人算法,电机控制,通信软件,PLC…...
nacos服务器启动报错集合
报错1 Error creating bean with name ‘user‘: Unsatisfied dependency expressed through field ‘jwtTokenManage 开启鉴权之后,你可以自定义用于生成JWT令牌的密钥,application.properties中的配置信息为: ### Since 1.4.1, worked when…...
C语言_分支和循环语句(2)
文章目录 前言一、for 循环1.1语法1.2 for 语句的循环控制变量1.3 一些 for 循环的变种 二、do ... while()循环2.1 do 语句的语法2.2 do ... while 循环中的 break 和 continue2.3 练习1 **- 计算n的阶乘**2. - **在一个有序数组中查找具体的某个数字 n** 二分查找算法&#x…...
JMeter 接口自动化测试:从入门到精通的完全指南
JMeter 是一个开源的负载测试工具,它可以模拟多种协议和应用程序的负载,包括 HTTP、FTP、SMTP、JMS、SOAP 和 JDBC 等。在进行接口自动化测试时,使用 JMeter 可以帮助我们快速地构建测试用例,模拟多种场景,发现接口的性…...
【Java】集合List的toArray()方法及其重载
在Java中,集合(List 接口的实现类)提供了一个名为 toArray 的方法,用于将集合中的元素转换成数组。该方法有两个主要的重载形式,分别用于不同的情况。 toArray()重载方法1 <T> T[] toArray(T[] a)这个方法将集…...
Python学习笔记:Requests库安装、通过url下载文件
1.下载安装requests库 在pipy或者github下载,通常是个zip,解压缩后在路径输入cmd,并运行以下代码 Python setup.py install 安装完成后,输入python再输入import requests得到可以判断时候完成安装 2.通过url下载文件 使用的是u…...
git pull --rebase 用法
git pull --rebase git pull --rebase 是 Git 命令中的一个选项,它的作用是在从远程仓库拉取更新时使用 rebase 而不是默认的合并方式。使用这个命令会使您的提交历史更加整洁,因为它将您的本地提交在远程更新之前重新应用到新的提交之上。 这个命令的…...
react antd框架中的徽标获取数据对应状态的数量
实现思路:获取数量的思路是通过filter过滤符合数据来实现。 列表数组.filter(item > item.status 值).length; 例子:以下这个例子是判断data数组中的status中在职的数量。 data.filter((item) > item.status 在职).length 效果展示ÿ…...
【多线程】Thread类的用法
文章目录 1. Thread类的创建1.1 自己创建类继承Thread类1.2 实现Runnable接口1.3 使用匿名内部类创建Thread子类对象1.4 使用匿名内部类创建Runnable子类对象1.5 使用lambda创建 2. Thread常见的构造方法2.1 Thread()2.2 Thread(Runnable target)2.3 Thread(String name)2.4 Th…...
第八章 贪心算法 part03 1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果 (day34补)
本文章代码以c为例! 一、力扣第1005题:K 次取反后最大化的数组和 题目: 给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组: 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择…...
Android Activity启动过程一:从Intent到Activity创建
关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。 目录 一、概览二、应用内启动源码流程 (startActivity)2.1 startActivit…...
第9章:聚类
聚类任务 性能度量 距离度量 非度量距离 原型聚类 有很好的统计学上的意义,但是只能找到椭球形的聚类。 密度聚类 层次聚类...
程序员为什么要写bug,不能一次性写好吗?
仅仅听到“Bug”这个词就会让你作为一个开发人员感到畏缩。我们相信,优秀的程序员是那些编写无错误代码的人。随着一些开发人员强调要成为一名零错误程序员,我们进行了更深刻的思考,并发现事实的准确性。 所有制作的软件都应该没有错误。对此…...
Nginx反向代理其他服务
Nginx反向代理 嘿,你的网络遇到了限制,不能直接通过服务的端口进行访问?别担心,我们可以借助Nginx这个超级英雄来解决这个问题!让我给你讲讲关于Nginx反向代理的故事吧。 首先,让我们明确一下反向代理的概…...
MQ 简介-RabbitMQ
一. MQ 简介 消息队列作为高并发系统的核心组件之一,能够帮助业务系统结构提升开发效率和系统 稳定性,消息队列主要具有以下特点: 削峰填谷:主要解决瞬时写压力大于应用服务能力导致消息丢失、系统奔溃等问题系统解耦:解决不同重要程度、不…...
强化学习(2)
强化学习(1) 1.多智能体深度强化学习重要性采样 多智能体深度强化学习(Multi-Agent Deep Reinforcement Learning,MADRL)是指在多智能体环境下使用深度强化学习算法进行协同学习。重要性采样(Importance Sampling)是…...
4大解决方案提升Blender项目质感:从资源获取到渲染优化的完整指南
4大解决方案提升Blender项目质感:从资源获取到渲染优化的完整指南 【免费下载链接】awesome-blender 🪐 A curated list of awesome Blender addons, tools, tutorials; and 3D resources for everyone. 项目地址: https://gitcode.com/GitHub_Trendin…...
Serge模型管理终极指南:如何快速下载、配置和优化AI模型
Serge模型管理终极指南:如何快速下载、配置和优化AI模型 【免费下载链接】serge A web interface for chatting with Alpaca through llama.cpp. Fully dockerized, with an easy to use API. 项目地址: https://gitcode.com/gh_mirrors/se/serge Serge是一个…...
3DS原生GBA游戏体验:open_agb_firm完整使用指南
3DS原生GBA游戏体验:open_agb_firm完整使用指南 【免费下载链接】open_agb_firm open_agb_firm is a bare metal app for running GBA homebrew/games using the 3DS builtin GBA hardware. 项目地址: https://gitcode.com/gh_mirrors/op/open_agb_firm 想要…...
从固定到自适应:手把手教你改进Savitzky-Golay滤波器,告别边界效应和参数调优烦恼
从固定到自适应:手把手教你改进Savitzky-Golay滤波器,告别边界效应和参数调优烦恼 信号处理领域的从业者常常面临一个两难选择:如何在去除噪声的同时,尽可能保留信号的关键特征?传统Savitzky-Golay滤波器虽然在一定程度…...
Cardano节点高级功能探索:质押池、智能合约与治理的终极指南
Cardano节点高级功能探索:质押池、智能合约与治理的终极指南 【免费下载链接】cardano-node The core component that is used to participate in a Cardano decentralised blockchain. 项目地址: https://gitcode.com/gh_mirrors/ca/cardano-node Cardano节…...
SegFormer完全指南:10分钟快速掌握基于Transformer的语义分割
SegFormer完全指南:10分钟快速掌握基于Transformer的语义分割 【免费下载链接】SegFormer Official PyTorch implementation of SegFormer 项目地址: https://gitcode.com/gh_mirrors/se/SegFormer SegFormer是一个简单、高效且强大的语义分割方法࿰…...
技术突破与环保价值:开源固件如何拯救戴森电池的生命周期
技术突破与环保价值:开源固件如何拯救戴森电池的生命周期 【免费下载链接】FU-Dyson-BMS (Unofficial) Firmware Upgrade for Dyson V6/V7 Vacuum Battery Management System 项目地址: https://gitcode.com/gh_mirrors/fu/FU-Dyson-BMS 当戴森V6/V7吸尘器突…...
STVD与STVP实战指南:从环境搭建到串口烧录全流程解析
1. STVD与STVP开发环境全解析 第一次接触STM8开发的朋友,往往会被STVD和STVP这两个工具搞得一头雾水。我刚开始用的时候也踩过不少坑,比如明明安装了STVD却编译不了C程序,烧录时总是提示设备保护。后来才发现,STM8开发需要工具链的…...
Python实战:出租车计费模拟器开发(附完整代码与测试用例)
Python实战:出租车计费模拟器开发(附完整代码与测试用例) 出租车计费系统是城市交通中不可或缺的一部分,而用Python模拟这一过程不仅能帮助初学者理解条件分支和输入输出处理,还能培养将现实问题转化为代码的思维能力。…...
3个关键技巧优化华硕笔记本性能:GHelper完全指南
3个关键技巧优化华硕笔记本性能:GHelper完全指南 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops. Control tool for ROG Zephyrus G14, G15, G16, M16, Flow X13, Flow X16, TUF, Strix, Scar and other models 项目地址: ht…...
