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

Flutter如何实现容器沿垂直方向上下拖拽至指定位置

垂直拖拽容器吸附指定点位实现方案

核心实现逻辑

整个功能拆成4个核心环节即可,不需要依赖复杂的手势库:

  • 提前配置停靠点位:根据需求预设容器需要停留的垂直位置坐标,比如示例里的底部收起位、展开位,统一用translateY偏移量做计算,性能远高于修改top属性
  • 绑定拖拽事件:同时兼容PC鼠标和移动端触摸场景,监听按下、移动、抬起三类事件,注意触摸事件要关闭passive配置才能阻止默认滚动冲突
  • 实时跟手位移:按下时记录初始触点坐标和容器当前偏移量,拖拽过程中实时计算位移差,动态更新容器位置,同时限制拖拽范围,避免拖出可视边界
  • 松手自动吸附:拖拽结束后,计算容器当前位置和所有预设点位的距离,自动吸附到最近的点位,吸附过程加CSS过渡保证动效顺滑

原生JS最简可运行示例

<style>
/* 可拖拽容器 */
.drag-card {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: 0;
  height: 75vh;
  background: #ffffff;
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -2px 12px rgba(0,0,0,0.1);
  /* 初始状态:只露出顶部40px拖拽条 */
  transform: translateY(calc(100% - 40px));
  /* 吸附动画 */
  transition: transform 0.28s cubic-bezier(0.25, 0.1, 0.25, 1);
  touch-action: none;
}
/* 顶部拖拽把手 */
.drag-handle {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.drag-handle::after {
  content: "";
  width: 42px;
  height: 4px;
  border-radius: 2px;
  background: #e0e0e0;
}
</style>

<div class="drag-card">
  <div class="drag-handle"></div>
  <!-- 容器自定义内容 -->
</div>

<script>
const card = document.querySelector('.drag-card')
// 预设两个停靠点的translateY值:0=完全展开,(容器高度-40px)=收起仅露把手
const stopPositions = [0, card.offsetHeight - 40]
let startClientY = 0
let startOffset = 0
let dragging = false

// 拖拽开始
const handleDragStart = (e) => {
  dragging = true
  startClientY = e.touches ? e.touches[0].clientY : e.clientY
  // 读取当前容器偏移量
  const transformMatrix = window.getComputedStyle(card).transform
  startOffset = transformMatrix !== 'none' ? Number(transformMatrix.split(',')[5].trim()) : 0
  // 拖拽时关闭过渡,保证跟手
  card.style.transition = 'none'
}

// 拖拽移动
const handleDragMove = (e) => {
  if (!dragging) return
  e.preventDefault()
  const currentClientY = e.touches ? e.touches[0].clientY : e.clientY
  const delta = currentClientY - startClientY
  let newOffset = startOffset + delta
  // 限制拖拽边界,不超出预设点位范围
  const maxOffset = Math.max(...stopPositions)
  const minOffset = Math.min(...stopPositions)
  newOffset = Math.max(minOffset, Math.min(newOffset, maxOffset))
  card.style.transform = `translateY(${newOffset}px)`
}

// 拖拽结束 吸附到最近点位
const handleDragEnd = () => {
  if (!dragging) return
  dragging = false
  card.style.transition = 'transform 0.28s cubic-bezier(0.25, 0.1, 0.25, 1)'
  // 取当前偏移量
  const currentOffset = Number(card.style.transform.match(/translateY\((\d+\.?\d*)px\)/)[1])
  // 找距离最近的停靠点
  const targetPosition = stopPositions.reduce((closest, pos) => {
    return Math.abs(pos - currentOffset) < Math.abs(closest - currentOffset) ? pos : closest
  })
  card.style.transform = `translateY(${targetPosition}px)`
}

// 绑定事件
card.addEventListener('mousedown', handleDragStart)
document.addEventListener('mousemove', handleDragMove)
document.addEventListener('mouseup', handleDragEnd)
card.addEventListener('touchstart', handleDragStart, { passive: false })
document.addEventListener('touchmove', handleDragMove, { passive: false })
document.addEventListener('touchend', handleDragEnd)
</script>

优化方向

  • 加阻尼效果:拖拽到边界附近时,位移量按比例折减,模拟真实物理手感
  • 加滑动速度判定:拖拽结束时如果纵向滑动速度超过阈值,直接吸附到滑动方向对应的点位,比纯距离判定更符合移动端交互直觉
  • 框架场景可以直接用成熟方案减少重复开发:Vue生态可用@vueuse/core的useDraggable组合式函数,React生态可用framer-motion自带的拖拽吸附能力,小程序直接用内置movable-view组件配置y轴阻尼和停靠点即可

实现效果参考:

  • 底部收起状态:container at bottom
  • 拖拽过程状态:dragged container

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:01:42