From ce1ff2d04dc2a20a16c83361b5b09b35a5e97eab Mon Sep 17 00:00:00 2001 From: imbytecat Date: Sun, 1 Mar 2026 05:49:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=94=A8=20Pointer=20Events=20=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=20touch+mouse=20=E4=BA=8B=E4=BB=B6=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E7=A7=BB=E5=8A=A8=E7=AB=AF=E5=8F=8C=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=8C=89=E9=92=AE=E5=8D=A1=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pointerdown/pointerup/pointerleave/pointercancel 统一处理所有输入 - 移除分离的 touchstart/touchend/mousedown/mouseup 事件绑定 - WS 断连时清理 pendingStart 状态,防止按钮永久卡死 - 添加 contextmenu 阻止长按弹出菜单 --- web/app.ts | 50 ++++++++++++++++++++------------------------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/web/app.ts b/web/app.ts index c6e4607..4c09bce 100644 --- a/web/app.ts +++ b/web/app.ts @@ -133,6 +133,12 @@ function connectWS(): void { state.ws = null; micBtn.disabled = true; if (state.recording) stopRecording(); + // Clean up pending async start on disconnect + if (state.pendingStart) { + state.pendingStart = false; + state.startCancelled = true; + micBtn.classList.remove("recording"); + } setStatus("disconnected", "已断开"); scheduleReconnect(); }; @@ -357,40 +363,24 @@ function escapeHtml(s: string): string { } // ── Event bindings ── function bindMicButton(): void { - // Touch events (mobile primary) - micBtn.addEventListener( - "touchstart", - (e: TouchEvent) => { - e.preventDefault(); - startRecording(); - }, - { passive: false }, - ); - micBtn.addEventListener( - "touchend", - (e: TouchEvent) => { - e.preventDefault(); - stopRecording(); - }, - { passive: false }, - ); - micBtn.addEventListener( - "touchcancel", - (e: TouchEvent) => { - e.preventDefault(); - stopRecording(); - }, - { passive: false }, - ); - // Mouse fallback (desktop testing) - micBtn.addEventListener("mousedown", (e: MouseEvent) => { + // Pointer Events: unified touch + mouse, no double-trigger + micBtn.addEventListener("pointerdown", (e: PointerEvent) => { if (e.button !== 0) return; + e.preventDefault(); startRecording(); }); - micBtn.addEventListener("mouseup", () => stopRecording()); - micBtn.addEventListener("mouseleave", () => { - if (state.recording) stopRecording(); + micBtn.addEventListener("pointerup", (e: PointerEvent) => { + e.preventDefault(); + stopRecording(); }); + micBtn.addEventListener("pointerleave", () => { + if (state.recording || state.pendingStart) stopRecording(); + }); + micBtn.addEventListener("pointercancel", () => { + if (state.recording || state.pendingStart) stopRecording(); + }); + // Prevent context menu on long press + micBtn.addEventListener("contextmenu", (e) => e.preventDefault()); } // ── Init ── function init(): void {