11<template >
2- <div class =" full-player-mobile" ref =" mobileStart" >
2+ <div
3+ class =" full-player-mobile"
4+ ref =" mobileStart"
5+ @touchstart.capture =" onTouchStart"
6+ @touchmove.capture =" onTouchMove"
7+ @touchend.capture =" onTouchEnd"
8+ >
39 <!-- 顶部功能栏 -->
410 <div class =" top-bar" >
511 <!-- 收起按钮 -->
1016
1117 <!-- 主内容 -->
1218 <div
13- :class =" ['mobile-content', { swiping: isSwiping }]"
19+ :class =" ['mobile-content', { swiping: isSwipingX }]"
1420 :style =" { transform: contentTransform }"
1521 @click.stop
1622 >
159165 <div
160166 v-if =" hasLyric"
161167 class =" lyric-overlay"
162- :class =" { swiping: isSwiping }"
168+ :class =" { swiping: isSwipingX }"
163169 :style =" { transform: lyricPageTransform }"
164170 >
165171 <PlayerLyric />
178184</template >
179185
180186<script setup lang="ts">
181- import { useSwipe } from " @vueuse/core" ;
182187import { useMusicStore , useStatusStore , useDataStore , useSettingStore } from " @/stores" ;
183188import { usePlayerController } from " @/core/player/PlayerController" ;
184189import { useTimeFormat } from " @/composables/useTimeFormat" ;
@@ -215,34 +220,80 @@ watch(hasLyric, (val) => {
215220
216221// 滑动偏移量
217222const swipeOffset = ref (0 );
223+ // 轴锁定:null=未确定, 'x'=水平翻页, 'y'=纵向滚动
224+ const axisLock = ref <" x" | " y" | null >(null );
225+ const isSwiping = ref (false );
226+ const lengthX = ref (0 );
218227
219- const { direction, isSwiping, lengthX } = useSwipe (mobileStart , {
220- threshold: 10 ,
221- onSwipe : () => {
222- if (! hasLyric .value ) return ;
223- // 为正表示向左滑,为负表示向右滑
224- swipeOffset .value = lengthX .value ;
225- },
226- onSwipeEnd : () => {
227- if (! hasLyric .value ) {
228- swipeOffset .value = 0 ;
229- return ;
228+ let startX = 0 ;
229+ let startY = 0 ;
230+
231+ const onTouchStart = (e : TouchEvent ) => {
232+ if (! hasLyric .value ) return ;
233+ startX = e .touches [0 ].clientX ;
234+ startY = e .touches [0 ].clientY ;
235+ axisLock .value = null ;
236+ isSwiping .value = true ;
237+ lengthX .value = 0 ;
238+ };
239+
240+ const onTouchMove = (e : TouchEvent ) => {
241+ if (! hasLyric .value || ! isSwiping .value ) return ;
242+
243+ const currentX = e .touches [0 ].clientX ;
244+ const currentY = e .touches [0 ].clientY ;
245+ const deltaX = startX - currentX ; // 左滑为正,符合 lengthX 语义
246+ const deltaY = startY - currentY ;
247+
248+ if (axisLock .value === null ) {
249+ if (Math .abs (deltaX ) > 10 || Math .abs (deltaY ) > 10 ) {
250+ axisLock .value = Math .abs (deltaX ) >= Math .abs (deltaY ) ? " x" : " y" ;
251+ // 确认是水平滑动时,在捕获阶段下发 touchcancel 终止子组件(如AMLL)内部的滑动状态
252+ if (axisLock .value === " x" && e .target instanceof EventTarget ) {
253+ e .target .dispatchEvent (new TouchEvent (" touchcancel" , { bubbles: true , cancelable: true }));
254+ }
230255 }
256+ }
257+
258+ if (axisLock .value === " x" ) {
259+ // 拦截后续的 touchmove 事件,避免歌词收到滑动导致滚动
260+ e .preventDefault ();
261+ e .stopPropagation ();
262+ lengthX .value = deltaX ;
263+ swipeOffset .value = lengthX .value ;
264+ }
265+ };
266+
267+ const onTouchEnd = (e : TouchEvent ) => {
268+ if (! hasLyric .value || ! isSwiping .value ) return ;
269+ isSwiping .value = false ;
270+
271+ if (axisLock .value === " x" ) {
272+ e .stopPropagation (); // 防止拦截事件造成点击误触
273+ const finalLengthX = startX - e .changedTouches [0 ].clientX ;
274+ const direction = finalLengthX > 0 ? " left" : " right" ;
275+
231276 // 超过阈值则切换页面
232- if (direction . value === " left" && lengthX . value > 100 ) {
277+ if (direction === " left" && finalLengthX > 100 ) {
233278 pageIndex .value = 1 ;
234- } else if (direction . value === " right" && lengthX . value < - 100 ) {
279+ } else if (direction === " right" && finalLengthX < - 100 ) {
235280 pageIndex .value = 0 ;
236281 }
237- swipeOffset .value = 0 ;
238- },
239- });
282+ }
283+
284+ swipeOffset .value = 0 ;
285+ axisLock .value = null ;
286+ lengthX .value = 0 ;
287+ };
288+
289+ // 仅水平轴锁定时才视为正在水平滑动
290+ const isSwipingX = computed (() => isSwiping .value && axisLock .value === " x" );
240291
241292// 计算歌词覆盖层的位移(与 mobile-content 内歌词页视觉同步,但在 stacking context 之外)
242293const lyricPageTransform = computed (() => {
243294 // pageIndex=0 时覆盖层在屏幕右侧,pageIndex=1 时回到屏幕内
244295 const baseOffset = (1 - pageIndex .value ) * 100 ;
245- if (! isSwiping .value || ! hasLyric .value ) {
296+ if (! isSwipingX .value || ! hasLyric .value ) {
246297 return ` translateX(${baseOffset }%) ` ;
247298 }
248299 let pixelOffset = lengthX .value ;
@@ -258,7 +309,7 @@ const lyricPageTransform = computed(() => {
258309// 计算实时的变换位置
259310const contentTransform = computed (() => {
260311 const baseOffset = pageIndex .value * 50 ; // 百分比
261- if (! isSwiping .value || ! hasLyric .value ) {
312+ if (! isSwipingX .value || ! hasLyric .value ) {
262313 return ` translateX(-${baseOffset }%) ` ;
263314 }
264315 let pixelOffset = lengthX .value ;
0 commit comments