Vue 3 实现抖音式卡片滑动交互:从零到完整方案

本文介绍如何用 Vue 3 + TypeScript 实现类似抖音的卡片滑动交互,应用于单词记忆场景。完整方案包含手势识别、堆叠动画、实时反馈和状态管理。

在线体验:m.dobell.top(手机浏览器打开)。


效果拆解

目标效果:屏幕中央一张主卡片,后方隐约可见两张堆叠卡片。手指左滑(不认识)或右滑(认识),卡片跟随手指移动并旋转,松开后飞出屏幕,下一张顶上。

核心需求:

  • 3 张可见卡片(z-index 分层,缩放递减)
  • Pointer Events 手势跟踪
  • 滑动过程实时视觉反馈(颜色、图标、标签)
  • 阈值判断 + 飞出动画 + 队列循环

卡片堆叠布局

三张卡片用 v-for 渲染,通过 index 控制层级和变形:

typescript 复制代码
const stackStyle = (stackIndex: number) => {
  if (stackIndex >= 3) return { display: 'none' }

  const scaleMap = [1, 0.95, 0.90]      // 越靠后越小
  const yOffsetMap = [0, 12, 24]         // 越靠后越向下
  const zIndexMap = [30, 29, 28]         // 越靠前越高

  return {
    zIndex: zIndexMap[stackIndex],
    transform: `scale(${scaleMap[stackIndex]}) translateY(${yOffsetMap[stackIndex]}px)`,
    transition: stackIndex === 0 && isDragging.value
      ? 'none'                           // 拖拽中不要过渡
      : 'transform 0.35s ease'
  }
}

数组用 ref<Word[]>([]) 存储当前队列。队列用完时自动从后端拉取下一批。


Pointer Events 手势跟踪

用 Pointer Events 而非 Touch Events,因为 Pointer Events 同时支持触屏和鼠标,方便开发调试:

typescript 复制代码
const SWIPE_THRESHOLD = 72  // px,超过此值触发飞出
const isDragging = ref(false)
const dragX = ref(0)

const onPointerDown = (e: PointerEvent) => {
  isDragging.value = true
  startX.value = e.clientX
  ;(e.target as HTMLElement).setPointerCapture(e.pointerId)
}

const onPointerMove = (e: PointerEvent) => {
  if (!isDragging.value) return
  dragX.value = e.clientX - startX.value
}

const onPointerUp = () => {
  if (!isDragging.value) return
  isDragging.value = false

  if (Math.abs(dragX.value) > SWIPE_THRESHOLD) {
    // 触发飞出
    const direction = dragX.value > 0 ? 'right' : 'left'
    flyAway(direction)
  } else {
    // 复位
    dragX.value = 0
  }
}

关键点:setPointerCapture 确保手指滑出元素边界后依然能跟踪。不用这个 API 的话,手指移出卡片范围就会丢失 tracking。


实时视觉反馈

根据拖拽距离动态计算颜色和图标透明度:

typescript 复制代码
const feedbackColor = computed(() => {
  const ratio = Math.min(Math.abs(dragX.value) / SWIPE_THRESHOLD, 1)
  if (dragX.value > 0) {
    return `rgba(34, 197, 94, ${ratio * 0.3})`  // 绿色 = 认识
  } else if (dragX.value < 0) {
    return `rgba(239, 68, 68, ${ratio * 0.3})`  // 红色 = 不认识
  }
  return 'transparent'
})

const checkIconOpacity = computed(() =>
  dragX.value > 0 ? Math.min(dragX.value / SWIPE_THRESHOLD, 1) : 0
)

const crossIconOpacity = computed(() =>
  dragX.value < 0 ? Math.min(-dragX.value / SWIPE_THRESHOLD, 1) : 0
)

拖拽距离与阈值的比值驱动所有视觉过渡。不需要动画库,computed 全搞定。


飞出动画 + 队列推进

飞出用 CSS transition + 最终位置计算:

typescript 复制代码
const flyAway = (direction: 'left' | 'right') => {
  const targetX = direction === 'right' ? window.innerWidth * 1.5 : -window.innerWidth * 1.5
  flyX.value = targetX
  flyRotation.value = direction === 'right' ? 20 : -20

  // 记录结果
  const result = direction === 'right' ? 'known' : 'forgotten'
  submitReview(currentWord.value.id, result)

  // 动画结束后移除当前词,推进队列
  setTimeout(() => {
    words.value.shift()
    flyX.value = 0
    flyRotation.value = 0
    dragX.value = 0
    // 队列快用完时预加载
    if (words.value.length < 5) fetchMoreWords()
  }, 350)
}

飞出期间新卡片已经在原来的第二张位置就位,视觉上无缝衔接。


四队列切换

高频词、大纲词、综合、错词巩固,四种模式对应不同 API 参数:

typescript 复制代码
type QueueMode = 'high' | 'outline' | 'mixed' | 'wrong'

const switchQueue = async (mode: QueueMode) => {
  currentMode.value = mode
  words.value = []
  await fetchWords(mode)
}

错词队列的每个词带有复习记录,后端根据复习次数和上次复习时间动态计算下次推送时机,实现间隔重复。


性能注意事项

  • 事件监听用 passive: false :Pointer Events 需要 preventDefault 防止页面滚动,所以要显式设置 { passive: false }
  • 动画用 transform 而非 left/top:transform 走合成层,不触发重排
  • 队列预加载:剩 5 张时自动拉取,滑动体验零等待

完整代码较长,核心逻辑即上面这些。这个交互方案可以直接复用到任何需要卡片滑动判断的场景------背单词、刷题、审阅、相亲......

在线体验:m.dobell.top,打开就能看到效果。注册即送 3 天会员,月卡 29 元。

相关推荐
_xaboy1 小时前
开源Vue组件FormCreate通过 JSON 生成TinyVue表单
前端·vue.js·低代码·开源·json·表单设计器
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_44:响应式设计——让网页适配所有屏幕的完整指南
前端·css·ui·html·tensorflow
折哥的程序人生 · 物流技术专研1 小时前
AI 编程与行业赋能|专栏总目录(持续更新)
开发语言·人工智能·软件工程·ai编程
前端不太难1 小时前
Edge AI 时代:从数据中心到终端,算力如何无处不在?
前端·人工智能·edge
Highcharts.js1 小时前
Highcharts v13 全新时间轴标签边界格式|让时间维度表达更智能
前端·信息可视化·时间序列·图表开发·chart·自定义标签·可视化开发
lichenyang4531 小时前
鸿蒙研读 10:@Provider/@Consumer、RelativeContainer、onNewWant
前端
大湿兄啊啊啊1 小时前
MID360S调试
java·服务器·前端
绺年1 小时前
模块化加载机制与循环依赖的探索
前端
Csvn2 小时前
前端技术 - 前端技术债务
前端