基于Vue3+TypeScript實(shí)現(xiàn)抖音式卡片滑動(dòng)交互功能
效果拆解
目標(biāo)效果:屏幕中央一張主卡片,后方隱約可見兩張堆疊卡片。手指左滑(不認(rèn)識(shí))或右滑(認(rèn)識(shí)),卡片跟隨手指移動(dòng)并旋轉(zhuǎn),松開后飛出屏幕,下一張頂上。
核心需求:
- 3 張可見卡片(z-index 分層,縮放遞減)
- Pointer Events 手勢(shì)跟蹤
- 滑動(dòng)過程實(shí)時(shí)視覺反饋(顏色、圖標(biāo)、標(biāo)簽)
- 閾值判斷 + 飛出動(dòng)畫 + 隊(duì)列循環(huán)
卡片堆疊布局
三張卡片用 v-for 渲染,通過 index 控制層級(jí)和變形:
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'
}
}
數(shù)組用 ref<Word[]>([]) 存儲(chǔ)當(dāng)前隊(duì)列。隊(duì)列用完時(shí)自動(dòng)從后端拉取下一批。
Pointer Events 手勢(shì)跟蹤
用 Pointer Events 而非 Touch Events,因?yàn)?Pointer Events 同時(shí)支持觸屏和鼠標(biāo),方便開發(fā)調(diào)試:
const SWIPE_THRESHOLD = 72 // px,超過此值觸發(fā)飛出
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) {
// 觸發(fā)飛出
const direction = dragX.value > 0 ? 'right' : 'left'
flyAway(direction)
} else {
// 復(fù)位
dragX.value = 0
}
}
關(guān)鍵點(diǎn):setPointerCapture 確保手指滑出元素邊界后依然能跟蹤。不用這個(gè) API 的話,手指移出卡片范圍就會(huì)丟失 tracking。
實(shí)時(shí)視覺反饋
根據(jù)拖拽距離動(dòng)態(tài)計(jì)算顏色和圖標(biāo)透明度:
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})` // 綠色 = 認(rèn)識(shí)
} else if (dragX.value < 0) {
return `rgba(239, 68, 68, ${ratio * 0.3})` // 紅色 = 不認(rèn)識(shí)
}
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
)
拖拽距離與閾值的比值驅(qū)動(dòng)所有視覺過渡。不需要?jiǎng)赢嫀?,computed 全搞定。
飛出動(dòng)畫 + 隊(duì)列推進(jìn)
飛出用 CSS transition + 最終位置計(jì)算:
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
// 記錄結(jié)果
const result = direction === 'right' ? 'known' : 'forgotten'
submitReview(currentWord.value.id, result)
// 動(dòng)畫結(jié)束后移除當(dāng)前詞,推進(jìn)隊(duì)列
setTimeout(() => {
words.value.shift()
flyX.value = 0
flyRotation.value = 0
dragX.value = 0
// 隊(duì)列快用完時(shí)預(yù)加載
if (words.value.length < 5) fetchMoreWords()
}, 350)
}
飛出期間新卡片已經(jīng)在原來的第二張位置就位,視覺上無縫銜接。
四隊(duì)列切換
高頻詞、大綱詞、綜合、錯(cuò)詞鞏固,四種模式對(duì)應(yīng)不同 API 參數(shù):
type QueueMode = 'high' | 'outline' | 'mixed' | 'wrong'
const switchQueue = async (mode: QueueMode) => {
currentMode.value = mode
words.value = []
await fetchWords(mode)
}
錯(cuò)詞隊(duì)列的每個(gè)詞帶有復(fù)習(xí)記錄,后端根據(jù)復(fù)習(xí)次數(shù)和上次復(fù)習(xí)時(shí)間動(dòng)態(tài)計(jì)算下次推送時(shí)機(jī),實(shí)現(xiàn)間隔重復(fù)。
性能注意事項(xiàng)
- 事件監(jiān)聽用 passive: false:Pointer Events 需要
preventDefault防止頁面滾動(dòng),所以要顯式設(shè)置{ passive: false } - 動(dòng)畫用 transform 而非 left/top:transform 走合成層,不觸發(fā)重排
- 隊(duì)列預(yù)加載:剩 5 張時(shí)自動(dòng)拉取,滑動(dòng)體驗(yàn)零等待
完整代碼較長,核心邏輯即上面這些。這個(gè)交互方案可以直接復(fù)用到任何需要卡片滑動(dòng)判斷的場景——背單詞、刷題、審閱、相親……
在線體驗(yàn):m.dobell.top,打開就能看到效果。
以上就是基于Vue3+TypeScript實(shí)現(xiàn)抖音式卡片滑動(dòng)交互功能的詳細(xì)內(nèi)容,更多關(guān)于Vue3 TypeScript抖音式卡片滑動(dòng)交互的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue+Vant實(shí)現(xiàn)7天日歷展示并在切換日期時(shí)實(shí)時(shí)變換功能
本文介紹了如何利用Vue和Vant框架結(jié)合moment.js插件來實(shí)現(xiàn)一個(gè)7天日歷展示功能,在這個(gè)功能中,用戶可以在切換日期時(shí)看到界面的實(shí)時(shí)變化,此外,文章還提供了代碼實(shí)現(xiàn)和效果測試的詳細(xì)步驟,幫助開發(fā)者能夠順利完成類似的項(xiàng)目開發(fā)2024-10-10
使用konva和vue-konva庫實(shí)現(xiàn)拖拽滑塊驗(yàn)證功能
這篇文章主要介紹了使用konva和vue-konva完成前端拖拽滑塊驗(yàn)證功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
elementUI實(shí)現(xiàn)級(jí)聯(lián)選擇器
這篇文章主要為大家詳細(xì)介紹了elementUI實(shí)現(xiàn)級(jí)聯(lián)選擇器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Webpack+Vue如何導(dǎo)入Jquery和Jquery的第三方插件
本文主要介紹了Webpack+Vue導(dǎo)入Jquery和Jquery的第三方插件的方法,具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02
vue watch監(jiān)聽變量值的實(shí)時(shí)變動(dòng)示例詳解
這篇文章主要介紹了vue 監(jiān)聽變量值的實(shí)時(shí)變動(dòng) watch,使用字符串形式的偵聽器函數(shù),還有一種是使用函數(shù)形式的偵聽器函數(shù),本文通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
vue.js+boostrap項(xiàng)目實(shí)踐(案例詳解)
這篇文章主要介紹了vue.js+boostrap項(xiàng)目實(shí)踐(案例詳解)的相關(guān)資料,本文圖文并茂介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
詳解@Vue/Cli 3 Invalid Host header 錯(cuò)誤解決辦法
這篇文章主要介紹了詳解@Vue/Cli 3 Invalid Host header 錯(cuò)誤解決辦法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01

