Update MkPullToRefresh.vue
This commit is contained in:
		
							parent
							
								
									4ee5811c2f
								
							
						
					
					
						commit
						84fe933863
					
				|  | @ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| <template> | ||||
| <div ref="rootEl"> | ||||
| 	<div v-if="isPullStart" :class="$style.frame" :style="`--frame-min-height: ${pullDistance / (PULL_BRAKE_BASE + (pullDistance / PULL_BRAKE_FACTOR))}px;`"> | ||||
| 	<div v-if="isPulling" :class="$style.frame" :style="`--frame-min-height: ${pullDistance / (PULL_BRAKE_BASE + (pullDistance / PULL_BRAKE_FACTOR))}px;`"> | ||||
| 		<div :class="$style.frameContent"> | ||||
| 			<MkLoading v-if="isRefreshing" :class="$style.loader" :em="true"/> | ||||
| 			<i v-else class="ti ti-arrow-bar-to-down" :class="[$style.icon, { [$style.refresh]: isPullEnd }]"></i> | ||||
| 			<i v-else class="ti ti-arrow-bar-to-down" :class="[$style.icon, { [$style.refresh]: isPulledEnough }]"></i> | ||||
| 			<div :class="$style.text"> | ||||
| 				<template v-if="isPullEnd">{{ i18n.ts.releaseToRefresh }}</template> | ||||
| 				<template v-if="isPulledEnough">{{ i18n.ts.releaseToRefresh }}</template> | ||||
| 				<template v-else-if="isRefreshing">{{ i18n.ts.refreshing }}</template> | ||||
| 				<template v-else>{{ i18n.ts.pullDownToRefresh }}</template> | ||||
| 			</div> | ||||
|  | @ -34,8 +34,8 @@ const RELEASE_TRANSITION_DURATION = 200; | |||
| const PULL_BRAKE_BASE = 1.5; | ||||
| const PULL_BRAKE_FACTOR = 170; | ||||
| 
 | ||||
| const isPullStart = ref(false); | ||||
| const isPullEnd = ref(false); | ||||
| const isPulling = ref(false); | ||||
| const isPulledEnough = ref(false); | ||||
| const isRefreshing = ref(false); | ||||
| const pullDistance = ref(0); | ||||
| 
 | ||||
|  | @ -44,8 +44,6 @@ let startScreenY: number | null = null; | |||
| const rootEl = useTemplateRef('rootEl'); | ||||
| let scrollEl: HTMLElement | null = null; | ||||
| 
 | ||||
| let disabled = false; | ||||
| 
 | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	refresher: () => Promise<void>; | ||||
| }>(), { | ||||
|  | @ -56,7 +54,7 @@ const emit = defineEmits<{ | |||
| 	(ev: 'refresh'): void; | ||||
| }>(); | ||||
| 
 | ||||
| function getScreenY(event) { | ||||
| function getScreenY(event: TouchEvent | MouseEvent): number { | ||||
| 	if (event.touches && event.touches[0] && event.touches[0].screenY != null) { | ||||
| 		return event.touches[0].screenY; | ||||
| 	} else { | ||||
|  | @ -64,21 +62,46 @@ function getScreenY(event) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| function moveStart(event) { | ||||
| 	if (!isPullStart.value && !isRefreshing.value && !disabled) { | ||||
| 		isPullStart.value = true; | ||||
| 		startScreenY = getScreenY(event); | ||||
| 		pullDistance.value = 0; | ||||
| // When at the top of the page, disable vertical overscroll so passive touch listeners can take over. | ||||
| function lockDownScroll() { | ||||
| 	scrollEl!.style.touchAction = 'pan-x pan-down pinch-zoom'; | ||||
| 	scrollEl!.style.overscrollBehavior = 'none'; | ||||
| } | ||||
| 
 | ||||
| function unlockDownScroll() { | ||||
| 	scrollEl!.style.touchAction = 'auto'; | ||||
| 	scrollEl!.style.overscrollBehavior = 'contain'; | ||||
| } | ||||
| 
 | ||||
| function moveStart(event: TouchEvent) { | ||||
| 	const scrollPos = scrollEl!.scrollTop; | ||||
| 	if (scrollPos === 0) { | ||||
| 		lockDownScroll(); | ||||
| 		if (!isPulling.value && !isRefreshing.value) { | ||||
| 			isPulling.value = true; | ||||
| 			startScreenY = getScreenY(event); | ||||
| 			pullDistance.value = 0; | ||||
| 			rootEl.value.addEventListener('touchmove', moving, { passive: true }); | ||||
| 			rootEl.value.addEventListener('touchend', () => { | ||||
| 				rootEl.value.removeEventListener('touchmove', moving); | ||||
| 				onPullRelease(); | ||||
| 			}, { passive: true, once: true }); | ||||
| 		} | ||||
| 	} else { | ||||
| 		unlockDownScroll(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function moveStartByMouse(event: MouseEvent) { | ||||
| 	if (!isPullStart.value && !isRefreshing.value && !disabled) { | ||||
| 		isPullStart.value = true; | ||||
| 	if (!isPulling.value && !isRefreshing.value) { | ||||
| 		isPulling.value = true; | ||||
| 		startScreenY = event.screenY; | ||||
| 		pullDistance.value = 0; | ||||
| 		window.addEventListener('mousemove', moving, { passive: false }); | ||||
| 		window.addEventListener('mouseup', moveEnd, { passive: true, once: true }); | ||||
| 		window.addEventListener('mousemove', moving, { passive: true }); | ||||
| 		window.addEventListener('mouseup', () => { | ||||
| 			window.removeEventListener('mousemove', moving); | ||||
| 			onPullRelease(); | ||||
| 		}, { passive: true, once: true }); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -118,31 +141,38 @@ async function closeContent() { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| function moveEnd() { | ||||
| 	if (isPullStart.value && !isRefreshing.value) { | ||||
| 		startScreenY = null; | ||||
| 		if (isPullEnd.value) { | ||||
| 			isPullEnd.value = false; | ||||
| 			isRefreshing.value = true; | ||||
| 			fixOverContent().then(() => { | ||||
| 				emit('refresh'); | ||||
| 				props.refresher().then(() => { | ||||
| 					refreshFinished(); | ||||
| 				}); | ||||
| function onPullRelease() { | ||||
| 	startScreenY = null; | ||||
| 	if (isPulledEnough.value) { | ||||
| 		isPulledEnough.value = false; | ||||
| 		isRefreshing.value = true; | ||||
| 		fixOverContent().then(() => { | ||||
| 			emit('refresh'); | ||||
| 			props.refresher().then(() => { | ||||
| 				refreshFinished(); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			closeContent().then(() => isPullStart.value = false); | ||||
| 		} | ||||
| 		}); | ||||
| 	} else { | ||||
| 		closeContent().then(() => isPulling.value = false); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function toggleScrollLockOnTouchEnd() { | ||||
| 	const scrollPos = scrollEl!.scrollTop; | ||||
| 	if (scrollPos === 0) { | ||||
| 		lockDownScroll(); | ||||
| 	} else { | ||||
| 		unlockDownScroll(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function moving(event: TouchEvent | PointerEvent | MouseEvent) { | ||||
| 	if (!isPullStart.value || isRefreshing.value || disabled) return; | ||||
| 	if (!isPulling.value || isRefreshing.value) return; | ||||
| 
 | ||||
| 	if ((scrollEl?.scrollTop ?? 0) > SCROLL_STOP + pullDistance.value || isHorizontalSwipeSwiping.value) { | ||||
| 		pullDistance.value = 0; | ||||
| 		isPullEnd.value = false; | ||||
| 		moveEnd(); | ||||
| 		isPulledEnough.value = false; | ||||
| 		onPullRelease(); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -154,15 +184,7 @@ function moving(event: TouchEvent | PointerEvent | MouseEvent) { | |||
| 	const moveHeight = moveScreenY - startScreenY!; | ||||
| 	pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE); | ||||
| 
 | ||||
| 	if (pullDistance.value > 0) { | ||||
| 		if (event.cancelable) event.preventDefault(); | ||||
| 	} | ||||
| 
 | ||||
| 	if (pullDistance.value > SCROLL_STOP) { | ||||
| 		event.stopPropagation(); | ||||
| 	} | ||||
| 
 | ||||
| 	isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD; | ||||
| 	isPulledEnough.value = pullDistance.value >= FIRE_THRESHOLD; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | @ -172,64 +194,25 @@ function moving(event: TouchEvent | PointerEvent | MouseEvent) { | |||
|  */ | ||||
| function refreshFinished() { | ||||
| 	closeContent().then(() => { | ||||
| 		isPullStart.value = false; | ||||
| 		isPulling.value = false; | ||||
| 		isRefreshing.value = false; | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function setDisabled(value) { | ||||
| 	disabled = value; | ||||
| } | ||||
| 
 | ||||
| function onScrollContainerScroll() { | ||||
| 	const scrollPos = scrollEl!.scrollTop; | ||||
| 
 | ||||
| 	// When at the top of the page, disable vertical overscroll so passive touch listeners can take over. | ||||
| 	if (scrollPos === 0) { | ||||
| 		scrollEl!.style.touchAction = 'pan-x pan-down pinch-zoom'; | ||||
| 		registerEventListenersForReadyToPull(); | ||||
| 	} else { | ||||
| 		scrollEl!.style.touchAction = 'auto'; | ||||
| 		unregisterEventListenersForReadyToPull(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function registerEventListenersForReadyToPull() { | ||||
| 	if (rootEl.value == null) return; | ||||
| 	rootEl.value.addEventListener('mousedown', moveStartByMouse, { passive: true }); | ||||
| 	rootEl.value.addEventListener('touchstart', moveStart, { passive: true }); | ||||
| 	rootEl.value.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない | ||||
| } | ||||
| 
 | ||||
| function unregisterEventListenersForReadyToPull() { | ||||
| 	if (rootEl.value == null) return; | ||||
| 	rootEl.value.removeEventListener('mousedown', moveStartByMouse); | ||||
| 	rootEl.value.removeEventListener('touchstart', moveStart); | ||||
| 	rootEl.value.removeEventListener('touchmove', moving); | ||||
| 	window.removeEventListener('mousemove', moving); | ||||
| } | ||||
| 
 | ||||
| onMounted(() => { | ||||
| 	if (rootEl.value == null) return; | ||||
| 
 | ||||
| 	scrollEl = getScrollContainer(rootEl.value); | ||||
| 	if (scrollEl == null) return; | ||||
| 
 | ||||
| 	scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true }); | ||||
| 
 | ||||
| 	rootEl.value.addEventListener('touchend', moveEnd, { passive: true }); | ||||
| 
 | ||||
| 	registerEventListenersForReadyToPull(); | ||||
| 	rootEl.value.addEventListener('mousedown', moveStartByMouse, { passive: true }); | ||||
| 	rootEl.value.addEventListener('touchstart', moveStart, { passive: true }); | ||||
| 	rootEl.value.addEventListener('touchend', toggleScrollLockOnTouchEnd, { passive: true }); | ||||
| }); | ||||
| 
 | ||||
| onUnmounted(() => { | ||||
| 	if (scrollEl) scrollEl.removeEventListener('scroll', onScrollContainerScroll); | ||||
| 
 | ||||
| 	unregisterEventListenersForReadyToPull(); | ||||
| }); | ||||
| 
 | ||||
| defineExpose({ | ||||
| 	setDisabled, | ||||
| 	rootEl.value.removeEventListener('mousedown', moveStartByMouse); | ||||
| 	rootEl.value.removeEventListener('touchstart', moveStart); | ||||
| 	rootEl.value.removeEventListener('touchend', toggleScrollLockOnTouchEnd); | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue