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

能否实现带横向可见月份拨盘与日期拨盘的Date picker?

横向滚动式日期选择器实现方案

当然可以实现你描述的这种横向滚动、中间位置选中的日期选择器,下面是具体的实现思路和代码示例:

核心实现要点

  • 双横向滚动容器:上层放置月份列表,下层放置对应月份的日期列表,通过同步滚动保持两者位置对应
  • 中间选中判定:监听滚动事件,计算容器视口中间位置对应的元素,将其标记为选中状态
  • 日期联动:切换月份时自动更新下层的日期列表,确保日期与月份匹配
  • 滚动对齐优化:利用滚动吸附特性,让滚动结束后选中元素自动对齐到容器中间,避免半选中状态

Vue 快速实现示例

模板部分

<template>
  <div class="date-picker-container">
    <!-- 月份滚动容器 -->
    <div ref="monthScroll" class="scroll-container" @scroll="handleScroll">
      <div 
        v-for="month in months" 
        :key="month.value"
        :data-value="month.value"
        :class="['month-item', { active: selectedMonth === month.value }]"
      >
        {{ month.label }}
      </div>
    </div>
    <!-- 日期滚动容器 -->
    <div ref="dayScroll" class="scroll-container" @scroll="handleScroll">
      <div 
        v-for="day in currentDays" 
        :key="day"
        :class="['day-item', { active: selectedDay === day }]"
      >
        {{ day }}
      </div>
    </div>
    <!-- 选中日期展示 -->
    <div class="selected-date">选中日期:{{ selectedMonthLabel }} {{ selectedDay }}日</div>
  </div>
</template>

脚本部分

<script setup>
import { ref, computed, onMounted } from 'vue'

const months = ref([
  { label: '1月', value: 1 },
  { label: '2月', value: 2 },
  { label: '3月', value: 3 },
  { label: '4月', value: 4 },
  { label: '5月', value: 5 },
  { label: '6月', value: 6 },
  { label: '7月', value: 7 },
  { label: '8月', value: 8 },
  { label: '9月', value: 9 },
  { label: '10月', value: 10 },
  { label: '11月', value: 11 },
  { label: '12月', value: 12 },
])

const monthScroll = ref(null)
const dayScroll = ref(null)
const selectedMonth = ref(new Date().getMonth() + 1)
const selectedDay = ref(new Date().getDate())

const selectedMonthLabel = computed(() => {
  return months.value.find(m => m.value === selectedMonth.value)?.label || ''
})

const currentDays = computed(() => {
  const daysInMonth = new Date(new Date().getFullYear(), selectedMonth.value, 0).getDate()
  return Array.from({ length: daysInMonth }, (_, i) => i + 1)
})

const handleScroll = (e) => {
  // 处理月份选中
  if (e.target === monthScroll.value) {
    const containerMid = monthScroll.value.scrollLeft + monthScroll.value.clientWidth / 2
    const items = monthScroll.value.querySelectorAll('.month-item')
    items.forEach(item => {
      const itemMid = item.offsetLeft + item.clientWidth / 2
      if (Math.abs(itemMid - containerMid) < item.clientWidth / 2) {
        selectedMonth.value = parseInt(item.dataset.value)
        // 同步日期滚动到选中日位置
        setTimeout(() => {
          const targetDay = dayScroll.value.querySelector(`.day-item:nth-child(${selectedDay.value})`)
          if (targetDay) {
            dayScroll.value.scrollLeft = targetDay.offsetLeft - (dayScroll.value.clientWidth / 2) + (targetDay.clientWidth / 2)
          }
        }, 0)
      }
    })
  }

  // 处理日期选中
  if (e.target === dayScroll.value) {
    const containerMid = dayScroll.value.scrollLeft + dayScroll.value.clientWidth / 2
    const items = dayScroll.value.querySelectorAll('.day-item')
    items.forEach(item => {
      const itemMid = item.offsetLeft + item.clientWidth / 2
      if (Math.abs(itemMid - containerMid) < item.clientWidth / 2) {
        selectedDay.value = parseInt(item.textContent)
        // 此处可添加加载当日条目的逻辑
        // loadDailyItems(selectedMonth.value, selectedDay.value)
      }
    })
  }
}

onMounted(() => {
  // 初始化滚动到当前日期位置
  const initMonthItem = monthScroll.value.querySelector(`.month-item:nth-child(${selectedMonth.value})`)
  const initDayItem = dayScroll.value.querySelector(`.day-item:nth-child(${selectedDay.value})`)
  if (initMonthItem) {
    monthScroll.value.scrollLeft = initMonthItem.offsetLeft - (monthScroll.value.clientWidth / 2) + (initMonthItem.clientWidth / 2)
  }
  if (initDayItem) {
    dayScroll.value.scrollLeft = initDayItem.offsetLeft - (dayScroll.value.clientWidth / 2) + (initDayItem.clientWidth / 2)
  }
})
</script>

样式部分

<style scoped>
.date-picker-container {
  width: 100%;
  max-width: 600px;
  margin: 20px auto;
}

.scroll-container {
  display: flex;
  overflow-x: auto;
  padding: 10px 0;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.scroll-container::-webkit-scrollbar {
  display: none;
}

.month-item, .day-item {
  flex-shrink: 0;
  width: 60px;
  text-align: center;
  padding: 8px 0;
  border-radius: 4px;
  cursor: pointer;
  scroll-snap-align: center;
  transition: background-color 0.2s;
}

.month-item.active, .day-item.active {
  background-color: #2196F3;
  color: white;
}

.selected-date {
  margin-top: 20px;
  text-align: center;
  font-size: 16px;
  font-weight: bold;
}
</style>

原生 JavaScript 实现思路

  1. 创建两个带横向滚动的 div 容器,分别承载月份和日期列表
  2. 动态生成月份选项和对应月份的日期选项
  3. 为两个容器绑定 scroll 事件,计算视口中间位置对应的元素,标记为选中状态
  4. 实现月份切换时的日期列表更新逻辑
  5. 利用 CSS 的 scroll-snap 属性优化滚动对齐体验

关键交互优化

  • 隐藏滚动条,让界面更简洁
  • 添加过渡动画,提升选中状态的视觉反馈
  • 滚动结束后自动触发当日条目的加载逻辑
  • 适配移动端触摸滑动操作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 22:33:20