fix(embed): pointer pause watchdog — capture does not cross iframe boundary, outside-release now auto-resumes in 2s
The earlier capture+window-listener fix is insufficient: releasing outside the 195x280 window dispatches pointerup in the host document, which no in-iframe listener can observe. Deterministic fallback: 2s watchdog on pointerdown force-clears the pause reason (in-frame releases still cancel it immediately). Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -201,6 +201,9 @@ let pointerMoved = 0;
|
|||||||
let lockedTarget: string | null = null;
|
let lockedTarget: string | null = null;
|
||||||
/** 拖动抑制一次 click。 */
|
/** 拖动抑制一次 click。 */
|
||||||
let suppressClick = false;
|
let suppressClick = false;
|
||||||
|
/** 按住看门狗(滚动卡死修复):up 丢失(窗外释放)时 2s 强制恢复滚动。 */
|
||||||
|
const POINTER_WATCHDOG_MS = 2000;
|
||||||
|
let pointerWatchdog: ReturnType<typeof setTimeout> | null = null;
|
||||||
let hoverPointerIsMouse = false;
|
let hoverPointerIsMouse = false;
|
||||||
|
|
||||||
function scheduleResume(): void {
|
function scheduleResume(): void {
|
||||||
@@ -219,14 +222,22 @@ function onPointerDown(event: PointerEvent): void {
|
|||||||
const anchor = (event.target as HTMLElement).closest<HTMLElement>('a.stream-photo');
|
const anchor = (event.target as HTMLElement).closest<HTMLElement>('a.stream-photo');
|
||||||
lockedTarget = anchor === null ? null : (anchor.dataset.photoId ?? null);
|
lockedTarget = anchor === null ? null : (anchor.dataset.photoId ?? null);
|
||||||
setReason('pointer', true);
|
setReason('pointer', true);
|
||||||
// 指针捕获(滚动卡死修复 2026-09-23):按下后拖出窗外松开时 viewport 收不到
|
// 指针捕获 + 看门狗(滚动卡死修复 2026-09-23):按下后拖出窗外松开时,
|
||||||
// pointerup,'pointer' 暂停原因会永久残留 → 滚动停死。捕获后 up/cancel 必回
|
// pointerup 发生在宿主文档——iframe 内任何监听都收不到(capture 不跨文档),
|
||||||
// 本元素;window 级兜底监听(onMounted)双保险。
|
// 'pointer' 暂停原因会永久残留 → 滚动停死。看门狗 2s 强制恢复是确定性兜底;
|
||||||
|
// capture/window 监听让 iframe 内的释放更快恢复。
|
||||||
try {
|
try {
|
||||||
viewportEl.value?.setPointerCapture(event.pointerId);
|
viewportEl.value?.setPointerCapture(event.pointerId);
|
||||||
} catch {
|
} catch {
|
||||||
/* 指针已释放等:忽略 */
|
/* 指针已释放等:忽略 */
|
||||||
}
|
}
|
||||||
|
if (pointerWatchdog !== null) clearTimeout(pointerWatchdog);
|
||||||
|
pointerWatchdog = setTimeout(() => {
|
||||||
|
pointerWatchdog = null;
|
||||||
|
pointerDownY = null;
|
||||||
|
setReason('pointer', false);
|
||||||
|
scheduleResume();
|
||||||
|
}, POINTER_WATCHDOG_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointerMove(event: PointerEvent): void {
|
function onPointerMove(event: PointerEvent): void {
|
||||||
@@ -244,6 +255,10 @@ function onPointerMove(event: PointerEvent): void {
|
|||||||
|
|
||||||
function onPointerUp(): void {
|
function onPointerUp(): void {
|
||||||
pointerDownY = null;
|
pointerDownY = null;
|
||||||
|
if (pointerWatchdog !== null) {
|
||||||
|
clearTimeout(pointerWatchdog);
|
||||||
|
pointerWatchdog = null;
|
||||||
|
}
|
||||||
setReason('pointer', false);
|
setReason('pointer', false);
|
||||||
scheduleResume();
|
scheduleResume();
|
||||||
}
|
}
|
||||||
@@ -373,6 +388,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
setReason('destroyed', true);
|
setReason('destroyed', true);
|
||||||
|
if (pointerWatchdog !== null) clearTimeout(pointerWatchdog);
|
||||||
layerEl.value?.clear();
|
layerEl.value?.clear();
|
||||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||||
window.removeEventListener('pointerup', onWindowPointerEnd);
|
window.removeEventListener('pointerup', onWindowPointerEnd);
|
||||||
|
|||||||
Reference in New Issue
Block a user