diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue index 4d936758c5..d3cb3897e7 100644 --- a/packages/frontend/src/components/MkPullToRefresh.vue +++ b/packages/frontend/src/components/MkPullToRefresh.vue @@ -43,6 +43,21 @@ const pullDistance = ref(0); let startScreenY: number | null = null; +let moveBySystemCancel: (() => void) | null = null; +let moveBySystemRafId: number | null = null; + +const onMouseMove = (event: MouseEvent) => moving(event); +const onMouseUp = () => { + window.removeEventListener('mousemove', onMouseMove); + onPullRelease(); +}; + +const onTouchMove = (event: TouchEvent) => moving(event); +const onTouchEnd = () => { + window.removeEventListener('touchmove', onTouchMove); + onPullRelease(); +}; + const rootEl = useTemplateRef('rootEl'); let scrollEl: HTMLElement | null = null; @@ -97,11 +112,8 @@ function moveStartByMouse(event: MouseEvent) { startScreenY = getScreenY(event); pullDistance.value = 0; - window.addEventListener('mousemove', moving, { passive: true }); - window.addEventListener('mouseup', () => { - window.removeEventListener('mousemove', moving); - onPullRelease(); - }, { passive: true, once: true }); + window.addEventListener('mousemove', onMouseMove, { passive: true }); + window.addEventListener('mouseup', onMouseUp, { passive: true, once: true }); } function moveStartByTouch(event: TouchEvent) { @@ -119,34 +131,70 @@ function moveStartByTouch(event: TouchEvent) { startScreenY = getScreenY(event); pullDistance.value = 0; - window.addEventListener('touchmove', moving, { passive: true }); - window.addEventListener('touchend', () => { - window.removeEventListener('touchmove', moving); - onPullRelease(); - }, { passive: true, once: true }); + window.addEventListener('touchmove', onTouchMove, { passive: true }); + window.addEventListener('touchend', onTouchEnd, { passive: true, once: true }); } function moveBySystem(to: number): Promise { + if (moveBySystemCancel != null) { + moveBySystemCancel(); + moveBySystemCancel = null; + } + return new Promise(r => { const startHeight = pullDistance.value; - const overHeight = pullDistance.value - to; - if (overHeight < 1) { + const overHeight = startHeight - to; + if (Math.abs(overHeight) < 1) { + pullDistance.value = to; r(); return; } - const startTime = Date.now(); - let intervalId = window.setInterval(() => { - const time = Date.now() - startTime; - if (time > RELEASE_TRANSITION_DURATION) { + + let startTime: DOMHighResTimeStamp | null = null; + let cancelled = false; + moveBySystemCancel = () => { + cancelled = true; + startTime = null; + if (moveBySystemRafId != null) { + window.cancelAnimationFrame(moveBySystemRafId); + moveBySystemRafId = null; + } + }; + + const tick = (now: DOMHighResTimeStamp) => { + if (cancelled) { + r(); + return; + } + if (startTime == null) { + startTime = now; + } + + const time = now - startTime; + if (time >= RELEASE_TRANSITION_DURATION) { pullDistance.value = to; - window.clearInterval(intervalId); + moveBySystemCancel = null; + moveBySystemRafId = null; r(); return; } const nextHeight = startHeight - (overHeight / RELEASE_TRANSITION_DURATION) * time; - if (pullDistance.value < nextHeight) return; + if (overHeight > 0) { + if (pullDistance.value < nextHeight) { + moveBySystemRafId = window.requestAnimationFrame(tick); + return; + } + } else { + if (pullDistance.value > nextHeight) { + moveBySystemRafId = window.requestAnimationFrame(tick); + return; + } + } pullDistance.value = nextHeight; - }, 1); + moveBySystemRafId = window.requestAnimationFrame(tick); + }; + + moveBySystemRafId = window.requestAnimationFrame(tick); }); } @@ -230,6 +278,14 @@ onMounted(() => { }); onUnmounted(() => { + if (moveBySystemCancel != null) { + moveBySystemCancel(); + moveBySystemCancel = null; + } + moveBySystemRafId = null; + // pull中にwindowへ登録したリスナーが残るのを防ぐ + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('touchmove', onTouchMove); unlockDownScroll(); if (rootEl.value) rootEl.value.removeEventListener('mousedown', moveStartByMouse); if (rootEl.value) rootEl.value.removeEventListener('touchstart', moveStartByTouch); diff --git a/packages/frontend/src/components/MkSwiper.vue b/packages/frontend/src/components/MkSwiper.vue index b66bfb0e9d..9476d21d83 100644 --- a/packages/frontend/src/components/MkSwiper.vue +++ b/packages/frontend/src/components/MkSwiper.vue @@ -7,9 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only