You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React.js项目中基于JavaScript实现自定义日历?

React 自定义日历实现方案

自定义日历参考效果
我在React项目里做过好几款和图示效果差不多的自定义日历,整体没有特别复杂的技术点,给你梳理可直接落地的实现思路:

基础选型

  • 日期计算逻辑直接用date-fns库就行,比moment轻量很多,计算当月起止日期、周偏移、相邻月日期、日期对比这些场景都有现成API,不用自己处理闰年、大小月天数的边界问题,能避开90%的日历bug。
  • 如果要做范围选择、多日期选中、日期禁用这类复杂交互,可以用react-day-picker做无样式的底层逻辑层,样式完全自己控制,比硬改antd、MUI这类组件库自带日历的默认样式省事很多;如果需求不复杂完全自己写状态也没多少代码。

组件拆分逻辑

整个日历拆成3个核心模块就行,状态统一存在最外层的日历父组件:

  • 头部导航区:放上下月/上下年切换按钮、当前展示年月的标题,维护一个currentMonth状态存当前视图对应月份的第一天Date对象,点切换按钮时更新这个状态即可。
  • 星期表头区:固定循环渲染周几的文案就行,不需要动态逻辑,根据产品需求决定周首是周日还是周一。
  • 日期网格区:这部分是核心,固定渲染6行*7列共42个日期格子,保证切换月份的时候日历高度固定不会抖动:
    1. 基于currentMonth算出当月第一天是周几,先在网格最前面补全对应数量的上月末尾日期
    2. 依次填入当月1号到当月最后一天的日期
    3. 剩下的空位补全下月开头的日期,凑够42个格子
    4. 每个格子单独判断状态:是否为选中日期/选中范围的起止/选中范围内的日期、是否为当天、是否为相邻月的灰显日期、是否为禁用日期,给不同状态的格子挂对应的类名即可。如果要做图示里的日期下的标记、日程提示,给格子留自定义渲染的插槽,传入以日期为key的配置对象做匹配渲染就行。

交互与样式注意点

  • 布局直接用CSS Grid实现日期网格,设置grid-template-columns: repeat(7, 1fr)就能自动均分7列宽度,比flex调间距方便很多。
  • 日期选择如果是单选就维护一个selectedDate状态存选中的Date对象;如果是范围选就维护rangeStart、rangeEnd两个状态,第一次点击赋值给start,第二次点击时如果日期晚于start就赋值给end,早于start就重置start为当前点击的日期即可。
  • 切月的时候可以给网格加个简单的淡入淡出或者位移动画,体验会好很多。

最小实现核心代码参考

import { 
  startOfMonth, endOfMonth, startOfWeek, endOfWeek, 
  addDays, format, isSameMonth, isSameDay 
} from 'date-fns'
import { useState } from 'react'

const CustomCalendar = () => {
  // 当前视图展示的月份
  const [currentMonth, setCurrentMonth] = useState(new Date())
  // 当前选中的日期
  const [selectedDate, setSelectedDate] = useState(new Date())

  // 生成日历网格需要的42个日期
  const renderCalendarCells = () => {
    const monthStart = startOfMonth(currentMonth)
    const calendarStart = startOfWeek(monthStart)
    const calendarEnd = endOfWeek(endOfMonth(monthStart))
    const cellList = []
    let currentDay = calendarStart

    while (currentDay <= calendarEnd) {
      cellList.push(currentDay)
      currentDay = addDays(currentDay, 1)
    }
    return cellList
  }

  return (
    <div className="custom-calendar-wrapper">
      {/* 头部导航 */}
      <div className="calendar-header">
        <button onClick={() => setCurrentMonth(addDays(currentMonth, -30))}>
          上月
        </button>
        <span className="current-month-text">
          {format(currentMonth, 'yyyy年MM月')}
        </span>
        <button onClick={() => setCurrentMonth(addDays(currentMonth, 30))}>
          下月
        </button>
      </div>

      {/* 星期表头 */}
      <div className="week-header-row">
        {['日','一','二','三','四','五','六'].map(weekText => (
          <div className="week-item" key={weekText}>{weekText}</div>
        ))}
      </div>

      {/* 日期网格 */}
      <div className="days-grid">
        {renderCalendarCells().map((dayItem, index) => (
          <div
            key={index}
            className={`
              day-cell
              ${!isSameMonth(dayItem, currentMonth) ? 'gray-other-month' : ''}
              ${isSameDay(dayItem, selectedDate) ? 'selected-style' : ''}
              ${isSameDay(dayItem, new Date()) ? 'is-today' : ''}
            `}
            onClick={() => setSelectedDate(dayItem)}
          >
            <span className="day-number">{format(dayItem, 'd')}</span>
            {/* 这里可以加自定义标记、日程提示的渲染逻辑 */}
          </div>
        ))}
      </div>
    </div>
  )
}

export default CustomCalendar

剩下的样式部分你对着参考图调整颜色、圆角、间距就行,没有特殊的实现难点。


内容的提问来源于stack exchange,提问作者Abdulaziz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 18:01:11