Skip to content

Commit 4d78e24

Browse files
committed
fix(mobile): 修复移动端歌词界面横纵向滑动同时进行
横纵向滑动会同时进行,如我想横向滑动切换到主页面却滑动了歌词,我想纵向滑动歌词却进行了横向滑动,导致体验不佳 神奇的 AI 改的,我也不懂喵(
1 parent 3f0087b commit 4d78e24

1 file changed

Lines changed: 73 additions & 22 deletions

File tree

src/components/Player/FullPlayerMobile.vue

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
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
<!-- 收起按钮 -->
@@ -10,7 +16,7 @@
1016

1117
<!-- 主内容 -->
1218
<div
13-
:class="['mobile-content', { swiping: isSwiping }]"
19+
:class="['mobile-content', { swiping: isSwipingX }]"
1420
:style="{ transform: contentTransform }"
1521
@click.stop
1622
>
@@ -159,7 +165,7 @@
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 />
@@ -178,7 +184,6 @@
178184
</template>
179185

180186
<script setup lang="ts">
181-
import { useSwipe } from "@vueuse/core";
182187
import { useMusicStore, useStatusStore, useDataStore, useSettingStore } from "@/stores";
183188
import { usePlayerController } from "@/core/player/PlayerController";
184189
import { useTimeFormat } from "@/composables/useTimeFormat";
@@ -215,34 +220,80 @@ watch(hasLyric, (val) => {
215220
216221
// 滑动偏移量
217222
const 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 之外)
242293
const 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
// 计算实时的变换位置
259310
const 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

Comments
 (0)