当前位置: 首页 > news >正文

原生 JS 实现自定义单选日历组件(无 UI 库、纯手写、禁用过去日期)

前言
在后台管理、表单页面开发中,日期选择是高频需求。项目中经常会遇到两种场景:一是不想引入Element UI、Ant Design等重型 UI 框架,减少打包体积;二是产品要求日历样式高度自定义,原生input[type=“date”]样式无法修改,无法满足设计稿。
本文基于原生 JavaScript + jQuery(仅引入基础库,DOM 简化操作) 从零手写一套独立悬浮单选日历,具备以下特性:
完全自定义样式,弹窗定位跟随输入框;
支持上 / 下一月、上 / 下一年快速切换;
自动区分上月 / 当月 / 下月日期,灰色置灰非当月天数;
核心业务逻辑:所有过去日期禁用不可点击,仅可选择今日及未来日期;
「今天」快捷按钮,一键选中当前日期并关闭弹窗;
点击空白区域自动关闭日历,防止弹窗滞留;
日期输出统一YYYY-MM-DD标准格式,直接适配后端接口;
低耦合结构,单文件开箱即用,可快速复用至任意表单。
全文附带完整可运行源码、逐行逻辑拆解、关键 API 说明、踩坑点总结,新手也能完全看懂,可直接落地项目。
一、整体结构分层设计
整个日历组件分为三层:输入框层、日历弹窗容器、日历内部渲染模块,结构完全解耦:
输入框 .date_input:只读文本框,内置日历图标,点击唤起弹窗;
弹窗容器 .calendar_wrap:fixed 悬浮定位,默认隐藏,点击输入框计算坐标展示;
日历内部模块
头部导航:年份 / 月份切换按钮 + 当前年月标题;
星期头部:周日到周六固定表头;
日期格子容器:动态渲染 42 格日期(固定 6 行 ×7 列,保证布局稳定);
底部快捷按钮:回到今日。
CSS 设计规范说明
输入框内置 svg 日历图标,无需引入图片资源,减少 HTTP 请求;
弹窗使用fixed定位,通过getBoundingClientRect实时获取输入框坐标,永远紧贴输入框下方;
样式分层隔离:日历所有样式统一包裹在.calendar_wrap下,不会污染页面其他元素;
状态类区分:
.other:上月 / 下月灰色无效日期;
.disabled:已过期、不可点击日期;
.active:当前选中日期高亮底色;
交互优化:hover 高亮有效日期、弹窗阴影分层,提升视觉体验。
二、完整可运行源码
html
预览

原生自定义单选日历组件
« ‹
2026年 Jun
› »
Su
Mo
Tu
We
Th
Fr
Sa
今天
<script> // 获取全局DOM元素,缓存节点减少重复DOM查询,提升性能 const inp = document.querySelector('.date_input'); const calWrap = document.getElementById('calendar'); const dayBox = document.getElementById('dayBox'); const calTitle = document.getElementById('calTitle'); const todayBtn = document.getElementById('todayBtn'); // 控制日历当前展示年月的基准日期 let nowDate = new Date(); // 月份英文简写映射,用于顶部标题渲染 const monthArr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // ===================== 1. 输入框点击唤起日历 ===================== inp.onclick = function (e) { // 阻止冒泡,避免触发body关闭弹窗事件 e.stopPropagation(); // getBoundingClientRect:获取输入框相对视口的坐标 const rect = inp.getBoundingClientRect(); // 弹窗定位:左对齐输入框,输入框下方8px展示 calWrap.style.left = rect.left + 'px'; calWrap.style.top = rect.bottom + 8 + 'px'; calWrap.style.display = 'block'; // 每次打开重新渲染当月日期(防止跨天打开日历日期不刷新) renderCal(); } // 点击页面空白处关闭日历弹窗 document.body.onclick = () => calWrap.style.display = 'none'; // 点击日历内部阻止冒泡,不会关闭弹窗 calWrap.onclick = e => e.stopPropagation(); // ===================== 2. 核心渲染函数 renderCal ===================== function renderCal() { // 获取当前系统今日,清空时分秒用于日期对比(仅比较年月日) const today = new Date(); today.setHours(0, 0, 0, 0); // 获取当前展示年份、月份 const y = nowDate.getFullYear(); const m = nowDate.getMonth(); // 渲染顶部年月标题 calTitle.innerText = `${y}年 ${monthArr[m]}`; // 清空上次渲染的日期格子 dayBox.innerHTML = ''; // 关键日期计算API说明 const firstDay = new Date(y, m, 1).getDay(); // 当月1号是星期几(0=周日~6=周六) const curMonthDays = new Date(y, m + 1, 0).getDate(); // 当前月份总天数 const lastMonthDays = new Date(y, m, 0).getDate(); // 上个月总天数 // 第一部分:渲染上月末尾剩余日期,全部置灰 .other for (let i = 0; i < firstDay; i++) { let d = lastMonthDays - firstDay + i + 1; dayBox.innerHTML += `<div class="day-item other">${d}</div>`; } // 第二部分:渲染当月完整日期(核心逻辑:判断过期禁用、选中高亮) for (let i = 1; i <= curMonthDays; i++) { const currentDate = new Date(y, m, i); currentDate.setHours(0, 0, 0, 0); let cls = ''; // 当前选中日期添加active高亮 if (i === nowDate.getDate()) cls = 'active'; // 日期小于今日,添加disabled禁用类,不可点击 if (currentDate < today) { cls += ' disabled'; } // padStart补零,统一输出 YYYY-MM-DD 标准日期格式 const formatMonth = String(m + 1).padStart(2, '0'); const formatDay = String(i).padStart(2, 0); dayBox.innerHTML += `<div class="day-item ${cls}">
http://www.jsqmd.com/news/1173348/

相关文章:

  • 3个核心模块深度解析:如何用UABEA解锁Unity资源编辑的无限可能
  • 神经算子:从传统神经网络到科学计算的AI革命
  • Cesium 热力图教程
  • MCP3551与PIC18F57Q43高精度数据采集方案详解
  • 【数据结构初阶】顺序表从0到1:增删查改代码实现详解
  • NCM文件解密技术:高效音频格式转换系统的架构设计与实现
  • 高精度信号采集:AD7175-8与PIC18LF46K42组合方案解析
  • 古法黄金饰品多品牌实测:工艺质感与保值性对比评测 - 招财兔数字员工
  • 3分钟搞定Godot游戏资源提取:终极PCK解包指南
  • 3类谷歌相机闪退根因分析:从服务依赖到配置错误的完整排查路径
  • 数据结构实战:用 Python 实现 3 种栈与 2 种队列(附复杂度对比)
  • 2026年智能听力训练技术全景:英语基础薄弱学生的突破路径
  • STM32F413RH与LV3296条形码扫描模块的硬件协同设计
  • 济南投资金条变现时机判断与实操参考方案:趋势研判、渠道甄别与全域落地指南 - 讯息早知道
  • OBS多平台直播效率提升实践指南:obs-multi-rtmp深度解析
  • 嵌入式系统电源管理方案:MAX77654与PIC18LF45K40实战
  • PySpark 3.5 RDD 算子性能对比:map vs mapPartitions 在 1000 万条数据下的吞吐量差异
  • AD5593R与TM4C129LNCZAD混合信号系统设计与实现
  • 一线观察:海南药食同源公司的真实表现
  • 2026年江苏省高职单招复读班行业服务白皮书 - 招财兔数字员工
  • FreeMove终极指南:3分钟解决C盘空间不足的烦恼
  • TB6593FNG与PIC18F85K90直流电机控制方案解析
  • AI入门三个月实战路线:从零基础到项目部署全流程
  • 如何彻底告别卡顿:原神帧率解锁工具完整指南
  • Unity手游内存优化实战:从策略到工具链的完整解决方案
  • LLM增强型测试驱动开发:契约驱动的TDD新范式
  • 模板驱动的文档自动化:从填空题到文档工厂
  • ADS7828与PIC18F47Q10构建高精度数据采集系统
  • 2026冰块配送Top3:江阴良臣制冰厂实测推荐 - 热点咨讯
  • Keil C51 中断号补丁 v1.0:3步安装,实测支持256中断向量