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

如何使用react-spring+decay让拖拽惯性停在最近的日历行位置

解决方案

你可以通过预先计算惯性滚动的最终目标位置,再将目标位置对齐到最近的行位置实现吸附效果,无需引入额外库,仅需修改现有react-spring逻辑即可。


步骤1:先定义基础常量

首先确定你的日历单行高度,以及提前声明滚动边界:

// 替换为你的日历组件实际单行高度
const ROW_HEIGHT = 60
// 和你现有useDrag配置的bounds保持一致
const MAX_SCROLL_TOP = (totalHeight - containerHeight) * -1
const MIN_SCROLL_TOP = 0

步骤2:修改松手后的动画逻辑

在useDrag的松手回调中,先计算惯性滚动的预估最终位置,对齐到最近的行后再启动动画,既保留原有的惯性手感,又能保证最终停在整行位置:

const bind = useDrag((state) => {
  const [, currentY] = state.movement
  const [, lastOffsetY] = state.lastOffset
  const offsetY = lastOffsetY + currentY

  if (state.down) {
    setOffsetY(offsetY)
    animatedOffsetYProps.set({ offsetY: offsetY })
  } else {
    // 计算惯性滚动的预估最终偏移量:react-spring默认decay规则下最终位移为 速度/0.1
    const velocity = state.direction[1] * state.velocity[1]
    const expectedFinalOffset = offsetY + velocity / 0.1
    // 对齐到最近的行位置
    const alignedOffset = Math.round(expectedFinalOffset / ROW_HEIGHT) * ROW_HEIGHT
    // 限制对齐后位置不超出滚动边界
    const clampedAlignedOffset = Math.max(MAX_SCROLL_TOP, Math.min(MIN_SCROLL_TOP, alignedOffset))
    // 启动弹簧动画到对齐后的位置,保留初始速度保证惯性手感
    animatedOffsetYProps.start({
      offsetY: clampedAlignedOffset,
      config: {
        velocity: velocity,
        frequency: 5.5,
        damping: 22 // 可调整阻尼控制吸附回弹手感,数值越大回弹越弱
      }
    })
  }
}, {
  from: [0, offsetY],
  bounds: { bottom: 0, top: (totalHeight - containerHeight) * -1 },
  rubberband: true
})

步骤3:调整初始useSpring配置

移除原配置中的decay: true,改为普通弹簧配置即可:

const [animatedOffsetY, animatedOffsetYProps] = useSpring(() => ({
  offsetY: 0,
  config: {
    frequency: 5.5,
  }
}))

可选优化

如果你的日历行高是动态不固定的,可以提前计算所有行的顶部偏移量数组,然后将expectedFinalOffset和数组中的值做对比,取最近的偏移量作为对齐目标即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 15:06:02