mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 21:12:49 +08:00
fix(web): 移动端进度条可拖动、歌曲行单击即播
移动端两个交互在触屏上是死的: 1) 迷你播放器的进度条只是展示,整行的 click 又被绑成跳转歌词页。现在这条 进度条用 Pointer Events(pointerdown/move/up/cancel + setPointerCapture) 支持点按和拖动 seek,一套代码同时服务触摸、手写笔和鼠标,手指滑出细条也 不会中断。可视轨道仍是 2px,但命中区域扩到 12px 并向下伸进播放器自身的 8px 内边距——传输按钮高 32px、在 42px 内容区里居中,上沿在 13px,正好错开。 拖动时渲染值切到手指位置,让 60fps 的 rAF 时钟别和手指抢(与音量条 #111 同源问题);本地覆盖在 seek 请求 resolve 之后才释放,避免先跳回旧位置再 跳到新位置。松手后 400ms 内的 click 被整行吞掉,否则 seek 完会被顺带导航 到歌词页。没有 transport 权限或时长未知时整条退回纯展示,并把 touch-action 还给页面,不会白吃掉滚动手势。 2) SongCard 和队列抽屉都用 @dblclick 触发播放,而 dblclick 是鼠标专属事件, 触屏永远不会触发。现在改为按事件判断:click 在现代浏览器里是 PointerEvent, pointerType 为 touch/pen 时单击播放,鼠标单击行为完全不变(双击仍然播放)。 用按事件判断而不是 matchMedia('(pointer: coarse)'),是因为后者只反映主指针, 在带触摸屏的笔记本上会判断错。选 click 而非 pointerup 也是有意的:浏览器 本就会抑制滑动手势末尾的 click,滑动列表时不会误触发播放。 队列行的移除按钮原先没有 @click.stop,加了行级 click 后会「点一下播放顺手 删掉」,一并补上,并按 SongCard 已有的约定在 coarse 指针下常显该按钮。 Closes #143 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
55695c2d1c
commit
f777d892db
3 files changed
+294
-14
No files matched your search
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="song-card" :class="{ active }" @dblclick="showPlay && $emit('play')">
|
||||
<div class="song-card" :class="{ active }" @click="onRowClick" @dblclick="showPlay && $emit('play')">
|
||||
<div class="song-index">{{ index }}</div>
|
||||
<CoverArt :url="song.coverUrl" :size="36" :radius="6" />
|
||||
<div class="song-info">
|
||||
@@ -51,12 +51,41 @@ const showPlay = computed(() => can('player.control') || guestCan('playNow'));
|
||||
const showPlayNext = computed(() => can('player.control') || guestCan('playNext'));
|
||||
const showAdd = computed(() => can('player.queue') || guestCan('addToQueue'));
|
||||
|
||||
defineEmits<{
|
||||
const emit = defineEmits<{
|
||||
play: [];
|
||||
playNext: [];
|
||||
add: [];
|
||||
}>();
|
||||
|
||||
/**
|
||||
* Was this click made with a finger/stylus rather than a mouse? (#143)
|
||||
*
|
||||
* `dblclick` is mouse-only and never fires on touch, so double-tap-to-play was
|
||||
* simply dead on phones. A single *tap* plays instead, while a single mouse
|
||||
* click must keep doing nothing — otherwise desktop behaviour changes and the
|
||||
* surviving dblclick would fire play twice.
|
||||
*
|
||||
* The check is per-EVENT, not per-device: a `click` is a PointerEvent in modern
|
||||
* browsers, so pointerType describes how *this* click was made. A global
|
||||
* matchMedia('(pointer: coarse)') check reports only the *primary* pointer and
|
||||
* is therefore wrong on hybrid laptops (touchscreen + trackpad); it is used only
|
||||
* as a fallback for browsers that give us no pointerType.
|
||||
*/
|
||||
function isTouchClick(e: MouseEvent): boolean {
|
||||
const pointerType = (e as PointerEvent).pointerType;
|
||||
if (pointerType) return pointerType === 'touch' || pointerType === 'pen';
|
||||
return window.matchMedia?.('(pointer: coarse)').matches ?? false;
|
||||
}
|
||||
|
||||
// Listening on `click` rather than `pointerup` on purpose: the browser already
|
||||
// suppresses the click that ended a scroll gesture, so a tap that was really the
|
||||
// start of a flick can't hijack playback for everyone in the channel.
|
||||
// (The action buttons stop propagation, so they never double-fire this.)
|
||||
function onRowClick(e: MouseEvent) {
|
||||
if (!showPlay.value || !isTouchClick(e)) return;
|
||||
emit('play');
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = seconds % 60;
|
||||
|
||||
Reference in new issue
Block a user