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

如何实现可滑动切换月份的动态日历/日期选择器?

实现Svelte日历组件的月份滑动切换功能

这里提供两种可行的解决方案,分别适配轻量无依赖场景和需要复杂滑动特性的场景:

方案一:原生触摸事件实现(无需第三方库)

这种方式轻量无依赖,适合简单场景,核心是通过监听触摸事件判断滑动方向,配合Svelte的状态管理和过渡动画完成切换。

实现步骤

  • 维护当前currentMonth和currentYear状态
  • 监听触摸起始、移动、结束事件,计算滑动距离判断方向
  • 根据滑动方向更新日期,自动处理月份边界(如1月切换到12月时年份减1,12月切换到1月时年份加1)
  • 添加过渡动画提升切换体验

代码示例(修改你的App.svelte)

<script>
  import CalendarMonth from './CalendarMonth.svelte';
  import { fly } from 'svelte/transition';

  let currentMonth = new Date().getMonth() + 1;
  let currentYear = new Date().getFullYear();
  let startX = 0;
  let isDragging = false;
  let slideDirection = 0;

  // 调整月份,自动处理年份边界
  const adjustMonth = (delta) => {
    const newMonth = currentMonth + delta;
    if (newMonth < 1) {
      currentMonth = 12;
      currentYear -= 1;
    } else if (newMonth > 12) {
      currentMonth = 1;
      currentYear += 1;
    } else {
      currentMonth = newMonth;
    }
  };

  const handleTouchStart = (e) => {
    startX = e.touches[0].clientX;
    isDragging = true;
  };

  const handleTouchMove = (e) => {
    if (!isDragging) return;
    const currentX = e.touches[0].clientX;
    slideDirection = currentX - startX;
  };

  const handleTouchEnd = () => {
    if (!isDragging) return;
    // 设置滑动阈值,超过50px才触发切换
    if (Math.abs(slideDirection) > 50) {
      slideDirection > 0 ? adjustMonth(-1) : adjustMonth(1);
    }
    isDragging = false;
    slideDirection = 0;
  };
</script>

<div 
  class="calendar-wrapper"
  on:touchstart={handleTouchStart}
  on:touchmove={handleTouchMove}
  on:touchend={handleTouchEnd}
  style="width: 100%; overflow: hidden; touch-action: pan-x;"
>
  {#key `${currentMonth}-${currentYear}`}
    <CalendarMonth 
      month={currentMonth} 
      year={currentYear}
      transition:fly={{ duration: 300, x: slideDirection > 0 ? -100 : 100 }}
    />
  {/key}
</div>

<style>
  .calendar-wrapper {
    user-select: none; /* 防止滑动时选中文字 */
  }
</style>

方案二:用Swiper.js实现(带虚拟滑块)

如果需要惯性滑动、触摸反馈等复杂特性,Swiper.js是合适的选择,之前失败大概率是虚拟滑块配置或集成方式问题,以下是正确实现:

实现步骤

  1. 安装依赖:npm install swiper
  2. 配置Swiper为单滑块模式,启用虚拟滑块(仅渲染当前及相邻月份,优化性能)
  3. 绑定slideChange事件,同步更新当前日期
  4. 动态维护需要渲染的日历列表,保持滑块始终处于中间位置

代码示例(修改你的App.svelte)

<script>
  import CalendarMonth from './CalendarMonth.svelte';
  import { Swiper, SwiperSlide } from 'swiper/svelte';
  import { Virtual } from 'swiper/modules';
  import 'swiper/css';
  import 'swiper/css/virtual';

  let currentDate = new Date();
  let currentIndex = 1; // 初始选中中间滑块

  // 生成当前、上一个、下一个月份的日历数据
  const getCalendarList = () => {
    const list = [];
    for (let i = -1; i <= 1; i++) {
      const date = new Date(currentDate);
      date.setMonth(date.getMonth() + i);
      list.push({
        month: date.getMonth() + 1,
        year: date.getFullYear()
      });
    }
    return list;
  };

  let calendarList = getCalendarList();

  const handleSlideChange = (swiper) => {
    const delta = swiper.activeIndex - currentIndex;
    currentDate.setMonth(currentDate.getMonth() + delta);
    currentIndex = 1; // 重置到中间滑块
    calendarList = getCalendarList();
    swiper.slideTo(currentIndex, 0); // 同步swiper位置
  };
</script>

<Swiper
  modules={[Virtual]}
  slidesPerView={1}
  spaceBetween={0}
  virtual
  initialSlide={currentIndex}
  on:slideChange={handleSlideChange}
  simulateTouch={true}
  touchRatio={1}
>
  {#each calendarList as item, idx (idx)}
    <SwiperSlide virtualIndex={idx}>
      <CalendarMonth month={item.month} year={item.year} />
    </SwiperSlide>
  {/each}
</Swiper>

注意事项

  • 两种方案都必须处理月份边界逻辑,避免出现无效月份
  • 原生方案可根据需求调整滑动阈值、动画类型和时长
  • Swiper方案如需分页器、导航按钮等功能,可添加对应模块和配置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 16:43:13