日期选择器
效果
2023年
2024年
2025年
2026年
2027年
2028年
2029年
2030年
2031年
2032年
1月
2月
3月
4月
5月
6月
7月
8月
9月
10月
11月
12月
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
思路
样式
外层容器固定高度并且设置
overflow:auto;position:relative
,内层容器自适应高度中间的日期选择器部分设置
position:absolute,top:50%
遮罩层也是一个绝对定位,使用
background-image:
html<div class="absolute inset-0 cursor-pointer pointer-events-none" style="background-image:linear-gradient( 180deg, hsla(0,0%,100%,1), hsla(0,0%,100%,0.8), hsla(0,0%,100%,0), hsla(0,0%,100%,0.8), hsla(0,0%,100%,1) )"> </div>
确定滚动位置
父容器使用scroll-snap-type
(scroll-snap-type CSS 属性定义在滚动容器中的一个临时点(snap point)如何被严格的执行)子容器使用
scroll-snap-align: center;
表示与滚动容器的对齐方式, 保证滚动到中间
联动
监听这三个的容器的滚动事件,根据滚动高度, 滚动高度 / 单个高度 计算出滚动下标
当滚动年份容器时,需要把 月份/天数 容器滚动到下标为 0 的位置
当滚动月份容器时,需要把 天数 容器滚动到下标为 0 的位置
监听滚动 年份/月份 的下标,计算对应的date个数
源码
vue
<template>
<div class="border border-solid border-red-400 rounded-md my-4 flex justify-between">
<div class="relative flex-1 cursor-pointer font-bold" v-for="d in data">
<div class="overflow-y-scroll h-[250px] snap-y snap-mandatory" :ref="getScrollRef">
<div class="test">
<div class="h-[50px] snap-center">
</div>
<div class="h-[50px] snap-center">
</div>
<div class="flex box-border justify-center items-center h-[50px] snap-center" v-for="(item, index) in d"
:key="index">
{{ item }}
</div>
<div class="h-[50px] snap-center">
</div>
<div class="h-[50px] snap-center">
</div>
</div>
</div>
<div
class="absolute left-0 -z-10 right-0 box-border h-[50px] top-[100px] border-gray-600 border-x-0 border-solid border-y">
</div>
<div class="absolute inset-0 cursor-pointer pointer-events-none" style="background-image:linear-gradient(
180deg,
hsla(0,0%,100%,1),
hsla(0,0%,100%,0.8),
hsla(0,0%,100%,0),
hsla(0,0%,100%,0.8),
hsla(0,0%,100%,1)
)">
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, reactive, watch } from "vue";
const years = ref(['2023年', '2024年', '2025年', '2026年', '2027年', '2028年', '2029年', '2030年', '2031年', '2032年'])
const months = ref(['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']);
const yearIndex = ref(0)
const monthIndex = ref(0)
const dates = ref([])
const scrollRefs = ref([])
const isScrollRefIndex = ref(-1)
watch(() => [yearIndex.value, monthIndex.value], ([yearIndex, monthIndex]) => {
const date = new Date(+years.value[yearIndex].slice(0, -1), +months.value[monthIndex].slice(0, -1), 0).getDate();
dates.value = Array.from({ length: date }, (_, i) => i + 1)
}, {
immediate: true
})
const data = computed(() => {
return [years.value, months.value, dates.value]
})
const findScrollIndex = (e: HTMLElement) => {
if (!e) return 0
const scrollTop = e.scrollTop;
const scrollIndex = scrollTop / 50;
return scrollIndex
}
const getScrollRef = (el: HTMLElement) => {
scrollRefs.value.push(el)
}
const handleScroll = () => {
scrollRefs.value.forEach((scrollRef, index) => {
if (isScrollRefIndex.value < index) {
scrollRef.scrollTo({
top: 0,
behavior: "smooth"
})
}
})
if (isScrollRefIndex.value === 0) {
yearIndex.value = Math.ceil(findScrollIndex(scrollRefs.value[0]))
} else if (isScrollRefIndex.value === 1) {
monthIndex.value = Math.ceil(findScrollIndex(scrollRefs.value[1]))
}
}
onMounted(() => {
scrollRefs.value.forEach((scrollRef, index) => {
scrollRef.addEventListener("scroll", () => {
isScrollRefIndex.value = index
handleScroll()
})
})
})
</script>
<style lang="scss" scoped>
::-webkit-scrollbar {
display: none;
}
</style>