enhance(frontend): PullToRefreshの改善 (MisskeyIO#218)
This commit is contained in:
		| @@ -31,6 +31,7 @@ import { getScrollContainer } from '@/scripts/scroll.js'; | |||||||
| const SCROLL_STOP = 10; | const SCROLL_STOP = 10; | ||||||
| const MAX_PULL_DISTANCE = Infinity; | const MAX_PULL_DISTANCE = Infinity; | ||||||
| const FIRE_THRESHOLD = 230; | const FIRE_THRESHOLD = 230; | ||||||
|  | const FIRE_THRESHOLD_RATIO = 1.1; | ||||||
| const RELEASE_TRANSITION_DURATION = 200; | const RELEASE_TRANSITION_DURATION = 200; | ||||||
| const PULL_BRAKE_BASE = 1.5; | const PULL_BRAKE_BASE = 1.5; | ||||||
| const PULL_BRAKE_FACTOR = 170; | const PULL_BRAKE_FACTOR = 170; | ||||||
| @@ -39,9 +40,11 @@ let isPullStart = $ref(false); | |||||||
| let isPullEnd = $ref(false); | let isPullEnd = $ref(false); | ||||||
| let isRefreshing = $ref(false); | let isRefreshing = $ref(false); | ||||||
| let pullDistance = $ref(0); | let pullDistance = $ref(0); | ||||||
|  | let moveRatio = $ref(0); | ||||||
|  |  | ||||||
| let supportPointerDesktop = false; | let supportPointerDesktop = false; | ||||||
| let startScreenY: number | null = null; | let startScreenY: number | null = null; | ||||||
|  | let startClientX: number | null = null; | ||||||
|  |  | ||||||
| const rootEl = $shallowRef<HTMLDivElement>(); | const rootEl = $shallowRef<HTMLDivElement>(); | ||||||
| let scrollEl: HTMLElement | null = null; | let scrollEl: HTMLElement | null = null; | ||||||
| @@ -63,11 +66,20 @@ function getScreenY(event) { | |||||||
| 	return event.touches[0].screenY; | 	return event.touches[0].screenY; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function getClientX(event) { | ||||||
|  | 	if (supportPointerDesktop) { | ||||||
|  | 		return event.clientX; | ||||||
|  | 	} | ||||||
|  | 	return event.touches[0].clientX; | ||||||
|  | } | ||||||
|  |  | ||||||
| function moveStart(event) { | function moveStart(event) { | ||||||
| 	if (!isPullStart && !isRefreshing && !disabled) { | 	if (!isPullStart && !isRefreshing && !disabled && scrollEl?.scrollTop === 0) { | ||||||
| 		isPullStart = true; | 		isPullStart = true; | ||||||
| 		startScreenY = getScreenY(event); | 		startScreenY = getScreenY(event); | ||||||
|  | 		startClientX = getClientX(event); | ||||||
| 		pullDistance = 0; | 		pullDistance = 0; | ||||||
|  | 		moveRatio = 0; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -110,6 +122,7 @@ async function closeContent() { | |||||||
| function moveEnd() { | function moveEnd() { | ||||||
| 	if (isPullStart && !isRefreshing) { | 	if (isPullStart && !isRefreshing) { | ||||||
| 		startScreenY = null; | 		startScreenY = null; | ||||||
|  | 		startClientX = null; | ||||||
| 		if (isPullEnd) { | 		if (isPullEnd) { | ||||||
| 			isPullEnd = false; | 			isPullEnd = false; | ||||||
| 			isRefreshing = true; | 			isRefreshing = true; | ||||||
| @@ -126,6 +139,7 @@ function moveEnd() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function moving(event: TouchEvent | PointerEvent) { | function moving(event: TouchEvent | PointerEvent) { | ||||||
|  | 	if (!isPullStart && scrollEl?.scrollTop === 0) moveStart(event); | ||||||
| 	if (!isPullStart || isRefreshing || disabled) return; | 	if (!isPullStart || isRefreshing || disabled) return; | ||||||
|  |  | ||||||
| 	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance)) { | 	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance)) { | ||||||
| @@ -135,19 +149,23 @@ function moving(event: TouchEvent | PointerEvent) { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (startScreenY === null) { | 	if (startScreenY === null || startClientX === null) { | ||||||
| 		startScreenY = getScreenY(event); | 		startScreenY = getScreenY(event); | ||||||
|  | 		startClientX = getClientX(event); | ||||||
| 	} | 	} | ||||||
| 	const moveScreenY = getScreenY(event); | 	const moveScreenY = getScreenY(event); | ||||||
|  | 	const moveClientX = getClientX(event); | ||||||
|  |  | ||||||
| 	const moveHeight = moveScreenY - startScreenY!; | 	const moveHeight = moveScreenY - startScreenY!; | ||||||
|  | 	const moveWidth = moveClientX - startClientX!; | ||||||
| 	pullDistance = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE); | 	pullDistance = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE); | ||||||
|  | 	moveRatio = Math.max(Math.abs(moveHeight), 1) / Math.max(Math.abs(moveWidth), 1); | ||||||
|  |  | ||||||
| 	if (pullDistance > 0) { | 	if (pullDistance > 0 && moveRatio > FIRE_THRESHOLD_RATIO) { | ||||||
| 		if (event.cancelable) event.preventDefault(); | 		if (event.cancelable) event.preventDefault(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	isPullEnd = pullDistance >= FIRE_THRESHOLD; | 	isPullEnd = pullDistance >= FIRE_THRESHOLD && moveRatio > FIRE_THRESHOLD_RATIO; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -167,47 +185,30 @@ function setDisabled(value) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function onScrollContainerScroll() { | function onScrollContainerScroll() { | ||||||
| 	const scrollPos = scrollEl!.scrollTop; |  | ||||||
|  |  | ||||||
| 	// When at the top of the page, disable vertical overscroll so passive touch listeners can take over. | 	// When at the top of the page, disable vertical overscroll so passive touch listeners can take over. | ||||||
| 	if (scrollPos === 0) { | 	if (scrollEl?.scrollTop === 0) { | ||||||
| 		scrollEl!.style.touchAction = 'pan-x pan-down pinch-zoom'; | 		scrollEl!.style.touchAction = 'pan-x pan-down pinch-zoom'; | ||||||
| 		registerEventListenersForReadyToPull(); |  | ||||||
| 	} else { | 	} else { | ||||||
| 		scrollEl!.style.touchAction = 'auto'; | 		scrollEl!.style.touchAction = 'auto'; | ||||||
| 		unregisterEventListenersForReadyToPull(); |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function registerEventListenersForReadyToPull() { |  | ||||||
| 	if (rootEl == null) return; |  | ||||||
| 	rootEl.addEventListener('touchstart', moveStart, { passive: true }); |  | ||||||
| 	rootEl.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function unregisterEventListenersForReadyToPull() { |  | ||||||
| 	if (rootEl == null) return; |  | ||||||
| 	rootEl.removeEventListener('touchstart', moveStart); |  | ||||||
| 	rootEl.removeEventListener('touchmove', moving); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
| 	if (rootEl == null) return; | 	if (rootEl == null) return; | ||||||
|  |  | ||||||
| 	scrollEl = getScrollContainer(rootEl); | 	scrollEl = getScrollContainer(rootEl); | ||||||
| 	if (scrollEl == null) return; | 	if (scrollEl == null) return; | ||||||
|  |  | ||||||
| 	scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true }); | 	scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true }); | ||||||
|  | 	rootEl.addEventListener('touchstart', moveStart, { passive: true }); | ||||||
|  | 	rootEl.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない | ||||||
| 	rootEl.addEventListener('touchend', moveEnd, { passive: true }); | 	rootEl.addEventListener('touchend', moveEnd, { passive: true }); | ||||||
|  |  | ||||||
| 	registerEventListenersForReadyToPull(); |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| onUnmounted(() => { | onUnmounted(() => { | ||||||
| 	if (scrollEl) scrollEl.removeEventListener('scroll', onScrollContainerScroll); | 	if (scrollEl) scrollEl.removeEventListener('scroll', onScrollContainerScroll); | ||||||
|  | 	if (rootEl == null) return; | ||||||
| 	unregisterEventListenersForReadyToPull(); | 	rootEl.removeEventListener('touchstart', moveStart); | ||||||
|  | 	rootEl.removeEventListener('touchmove', moving); | ||||||
|  | 	rootEl.removeEventListener('touchend', moveEnd); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| defineExpose({ | defineExpose({ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 まっちゃとーにゅ
					まっちゃとーにゅ