实现效果

日期选择器

实现思路

  1. 根据年月实时计算出该月有多少天
  2. 根据对应的年月日渲染出元素
  3. 初始化当前日期->根据传入的日期在日期列表里找出对应下标,然后根据每个元素的固定高度算出滚动条的 TOP
  4. 监听对应元素的滚动条滚动结束事件->然后根据滚动条的 TOP 算出对应的下标。
  5. 根据下标实时更新当前的日期,然后重新渲染出对应的年月日。
  6. 在动态设置当前日期时,调用实例成员传入的回调函数并注入当前日期以达到实时返回当前日期的目的

实现代码

  • html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<div class="box">
<div class="scroll">
<div class="target"></div>
<div class="mask"></div>
<div class="wrapper year-wrapper">
<div class="scroll-wrapper"></div>
</div>
</div>
<div class="scroll">
<div class="target"></div>
<div class="mask"></div>
<div class="wrapper month-wrapper">
<div class="scroll-wrapper"></div>
</div>
</div>
<div class="scroll">
<div class="target"></div>
<div class="mask"></div>
<div class="wrapper day-wrapper">
<div class="scroll-wrapper"></div>
</div>
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
  • css
body {
margin: 0;
padding: 0;
}

.box {
display: flex;
flex-direction: row;
margin-top: 20px;
width: 500px;
height: 250px;
}

.box .scroll {
width: 100%;
height: 100%;
position: relative;
}

.box .scroll .target {
position: absolute;
width: 100%;
height: 50px;
top: 100px;
left: 0;
z-index: -1;
display: flex;
border-top: 1px solid #999;
border-bottom: 1px solid #999;
color: #000;
font-size: 16px;
}

.box .scroll .mask {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background-image: linear-gradient(
180deg,
white,
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.8),
white
);
pointer-events: none;
}

.box .scroll .wrapper {
overflow: auto;
height: 250px;
scroll-snap-type: y mandatory;
}

.box .scroll .wrapper .item {
display: flex;
justify-content: center;
align-items: center;
height: 50px;
scroll-snap-align: center;
color: #000;
font-size: 16px;
}

::-webkit-scrollbar {
display: none;
}
  • js
class DatePicker {
#yearArr = []; //支持的年份
#monthArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; //月份
#dayArr = []; //天数
#currDate = []; //当前日期
#yearWrapper = document.querySelector(".year-wrapper");
#monthWrapper = document.querySelector(".month-wrapper");
#dayWrapper = document.querySelector(".day-wrapper");
/**
* @param {Array} yearArr 传入支持的年限
* @param {Array<number>(3)} currDate 当前日期【年,月,日】
*/
constructor(yearArr, currDate) {
this.#yearArr = yearArr;
this.#currDate = currDate;
this.#dayArr = this.#createDays(currDate);
this.#render();
this.#handleScrolls();
}
/**
* 根据传入的年月来得到天数
* @param {number} year 年
* @param {number} month 月
* @returns {Array} 返回当前月的 天数列表
*/
#createDays([year, month]) {
const days = new Date(year, month, 0).getDate();
return Array.from({ length: days }, (v, i) => i + 1);
}
/**
* 创建单个元素
* @returns {HTMLElement} 返回创建的元素
*/
#createItem() {
const div = document.createElement("div");
div.classList.add("item");
return div;
}
/**
* 根据传入的id选择器与对应数据进行创建html元素片段
* @param {HTMLElement} wrapper 传入的类选择器
* @param {Array} arr 传入的数据
*/
#createItems(wrapper, arr) {
//文档片段
const fragment = document.createDocumentFragment();
const scrollWrapper = wrapper.querySelector(".scroll-wrapper");
//留空的元素
for (let i = 0; i < 2; i++) {
const div = this.#createItem();
fragment.appendChild(div);
}
for (let i = 0, len = arr.length; i < len; i++) {
const div = this.#createItem();
div.innerText = arr[i];
fragment.appendChild(div);
}
//留空的元素
for (let i = 0; i < 2; i++) {
const div = this.#createItem();
fragment.appendChild(div);
}
//每次渲染都清空原来的内容
scrollWrapper.innerHTML = "";
//插入元素
scrollWrapper.appendChild(fragment);
}
//渲染
#render() {
//渲染年份
this.#createItems(this.#yearWrapper, this.#yearArr);
//渲染月份
this.#createItems(this.#monthWrapper, this.#monthArr);
//渲染天数
this.#createItems(this.#dayWrapper, this.#dayArr);
//初始化当前日期
this.#initCurrDates(this.#currDate);
}
/**
* 初始化当前日期
* @param {Array} currDate 当前日期
*/
#initCurrDates(currDate) {
const [year, month, day] = currDate;
//初始化单个日期
this.#initCurrDate(this.#yearArr, year, this.#yearWrapper);
this.#initCurrDate(this.#monthArr, month, this.#monthWrapper);
this.#initCurrDate(this.#dayArr, day, this.#dayWrapper);
}
/**
* 初始化当前的单个日期
* @param {Array} arr 当前时间的数组
* @param {number} currDate 当前日期
* @param {HTMLElement} wrapper 当前的类选择器
*/
#initCurrDate(arr, currDate, wrapper) {
const index = arr.indexOf(currDate);
wrapper.scrollTop = index * 50;
}
/**
* 监听三个滚动条的结束滚动事件
*/
#handleScrolls() {
this.#handleScroll(this.#yearWrapper, "year");
this.#handleScroll(this.#monthWrapper, "month");
this.#handleScroll(this.#dayWrapper, "day");
}
/**
* 监听单个滚动条的结束滚动事件
* @param {HTMLElement} domId 当前监听的类选择器
* @param {string } fileId 当前监听的类型
*/
#handleScroll(domId, fileId) {
domId.addEventListener(
"scrollend",
this.#setCurrDate.bind(this, domId, fileId)
);
}
/**
* 设置当前显示的日期
* @param {HTMLElement} dom 当前需要设置日期的类选择器
* @param {string} fileId 标识当前需要设置日期的类型
*/
#setCurrDate(dom, fileId) {
//根据滚动条的位置来得到当前的日期在日期列表的下标
const index = dom.scrollTop / 50;
switch (fileId) {
case "year":
//设置当前的年份
this.#currDate[0] = this.#yearArr[index];
//设置当前的月份日
this.#setDayData(this.#currDate);
break;

case "month":
this.#currDate[1] = this.#monthArr[index];
this.#setDayData(this.#currDate);
break;

case "day":
this.#currDate[2] = this.#dayArr[index];
break;

default:
console.log("setCurrDateError");
break;
}
//当前年份变化时,执行实例成员传入的回调函数并注入当前日期
this.callback && this.callback(this.#currDate);
}
/**
* 根据传入的年月来得到天数
* @param {number} year 当前的年
* @param {number} month 当前的月
*/
#setDayData(currDate) {
this.#dayArr = this.#createDays(currDate);
this.#createItems(this.#dayWrapper, this.#dayArr);
}
/**
* 获取当前日期
* @returns {Array} 返回当前的日期
*/
getCurrDate() {
return this.#currDate;
}
/**
* 监听日期的变化实时返回日期
* @param {Function} callback 回调函数
*/
watch(callback) {
typeof callback === "function" && (this.callback = callback);
}
}
const dateArr = [
2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022,
2023, 2024,
];
const currDate = new Date()
.toLocaleDateString()
.split("/")
.map((i) => Number(i));
const d = new DatePicker(dateArr, currDate);
d.watch((date) => {
console.log("watch", date);
});