enhance(frontend/HorizontalSwipe): 操作性の改善 (#13038)
* Update swipe thresholds and touch-action * スワイプ中にPullToRefreshが反応しないように * 横スワイプに関与する可能性のある要素がある場合はスワイプを発火しないように * update threshold * isSwipingを外部化 * rename --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
parent
313ce82192
commit
155896a851
3 changed files with 39 additions and 6 deletions
|
@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, shallowRef, computed, nextTick, watch } from 'vue';
|
import { ref, shallowRef, computed, nextTick, watch } from 'vue';
|
||||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
import { isHorizontalSwipeSwiping as isSwiping } from '@/scripts/touch.js';
|
||||||
|
|
||||||
const rootEl = shallowRef<HTMLDivElement>();
|
const rootEl = shallowRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
@ -49,16 +49,16 @@ const shouldAnimate = computed(() => defaultStore.reactiveState.enableHorizontal
|
||||||
// ▼ しきい値 ▼ //
|
// ▼ しきい値 ▼ //
|
||||||
|
|
||||||
// スワイプと判定される最小の距離
|
// スワイプと判定される最小の距離
|
||||||
const MIN_SWIPE_DISTANCE = 50;
|
const MIN_SWIPE_DISTANCE = 20;
|
||||||
|
|
||||||
// スワイプ時の動作を発火する最小の距離
|
// スワイプ時の動作を発火する最小の距離
|
||||||
const SWIPE_DISTANCE_THRESHOLD = 125;
|
const SWIPE_DISTANCE_THRESHOLD = 70;
|
||||||
|
|
||||||
// スワイプを中断するY方向の移動距離
|
// スワイプを中断するY方向の移動距離
|
||||||
const SWIPE_ABORT_Y_THRESHOLD = 75;
|
const SWIPE_ABORT_Y_THRESHOLD = 75;
|
||||||
|
|
||||||
// スワイプできる最大の距離
|
// スワイプできる最大の距離
|
||||||
const MAX_SWIPE_DISTANCE = 150;
|
const MAX_SWIPE_DISTANCE = 120;
|
||||||
|
|
||||||
// ▲ しきい値 ▲ //
|
// ▲ しきい値 ▲ //
|
||||||
|
|
||||||
|
@ -68,7 +68,6 @@ let startScreenY: number | null = null;
|
||||||
const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
|
const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
|
||||||
|
|
||||||
const pullDistance = ref(0);
|
const pullDistance = ref(0);
|
||||||
const isSwiping = ref(false);
|
|
||||||
const isSwipingForClass = ref(false);
|
const isSwipingForClass = ref(false);
|
||||||
let swipeAborted = false;
|
let swipeAborted = false;
|
||||||
|
|
||||||
|
@ -77,6 +76,8 @@ function touchStart(event: TouchEvent) {
|
||||||
|
|
||||||
if (event.touches.length !== 1) return;
|
if (event.touches.length !== 1) return;
|
||||||
|
|
||||||
|
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
||||||
|
|
||||||
startScreenX = event.touches[0].screenX;
|
startScreenX = event.touches[0].screenX;
|
||||||
startScreenY = event.touches[0].screenY;
|
startScreenY = event.touches[0].screenY;
|
||||||
}
|
}
|
||||||
|
@ -90,6 +91,8 @@ function touchMove(event: TouchEvent) {
|
||||||
|
|
||||||
if (swipeAborted) return;
|
if (swipeAborted) return;
|
||||||
|
|
||||||
|
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
||||||
|
|
||||||
let distanceX = event.touches[0].screenX - startScreenX;
|
let distanceX = event.touches[0].screenX - startScreenX;
|
||||||
let distanceY = event.touches[0].screenY - startScreenY;
|
let distanceY = event.touches[0].screenY - startScreenY;
|
||||||
|
|
||||||
|
@ -139,6 +142,8 @@ function touchEnd(event: TouchEvent) {
|
||||||
|
|
||||||
if (!isSwiping.value) return;
|
if (!isSwiping.value) return;
|
||||||
|
|
||||||
|
if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
|
||||||
|
|
||||||
const distance = event.changedTouches[0].screenX - startScreenX;
|
const distance = event.changedTouches[0].screenX - startScreenX;
|
||||||
|
|
||||||
if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) {
|
if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) {
|
||||||
|
@ -162,6 +167,24 @@ function touchEnd(event: TouchEvent) {
|
||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 横スワイプに関与する可能性のある要素を調べる */
|
||||||
|
function hasSomethingToDoWithXSwipe(el: HTMLElement) {
|
||||||
|
if (['INPUT', 'TEXTAREA'].includes(el.tagName)) return true;
|
||||||
|
if (el.isContentEditable) return true;
|
||||||
|
if (el.scrollWidth > el.clientWidth) return true;
|
||||||
|
|
||||||
|
const style = window.getComputedStyle(el);
|
||||||
|
if (['absolute', 'fixed', 'sticky'].includes(style.position)) return true;
|
||||||
|
if (['scroll', 'auto'].includes(style.overflowX)) return true;
|
||||||
|
if (style.touchAction === 'pan-x') return true;
|
||||||
|
|
||||||
|
if (el.parentElement && el.parentElement !== rootEl.value) {
|
||||||
|
return hasSomethingToDoWithXSwipe(el.parentElement);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const transitionName = ref<'swipeAnimationLeft' | 'swipeAnimationRight' | undefined>(undefined);
|
const transitionName = ref<'swipeAnimationLeft' | 'swipeAnimationRight' | undefined>(undefined);
|
||||||
|
|
||||||
watch(tabModel, (newTab, oldTab) => {
|
watch(tabModel, (newTab, oldTab) => {
|
||||||
|
@ -182,6 +205,7 @@ watch(tabModel, (newTab, oldTab) => {
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.transitionRoot {
|
.transitionRoot {
|
||||||
|
touch-action: pan-y pinch-zoom;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100%;
|
grid-template-columns: 100%;
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
|
|
|
@ -26,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
|
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { getScrollContainer } from '@/scripts/scroll.js';
|
import { getScrollContainer } from '@/scripts/scroll.js';
|
||||||
|
import { isHorizontalSwipeSwiping } from '@/scripts/touch.js';
|
||||||
|
|
||||||
const SCROLL_STOP = 10;
|
const SCROLL_STOP = 10;
|
||||||
const MAX_PULL_DISTANCE = Infinity;
|
const MAX_PULL_DISTANCE = Infinity;
|
||||||
|
@ -129,7 +130,7 @@ function moveEnd() {
|
||||||
function moving(event: TouchEvent | PointerEvent) {
|
function moving(event: TouchEvent | PointerEvent) {
|
||||||
if (!isPullStart.value || isRefreshing.value || disabled) return;
|
if (!isPullStart.value || isRefreshing.value || disabled) return;
|
||||||
|
|
||||||
if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value)) {
|
if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value) || isHorizontalSwipeSwiping.value) {
|
||||||
pullDistance.value = 0;
|
pullDistance.value = 0;
|
||||||
isPullEnd.value = false;
|
isPullEnd.value = false;
|
||||||
moveEnd();
|
moveEnd();
|
||||||
|
@ -148,6 +149,10 @@ function moving(event: TouchEvent | PointerEvent) {
|
||||||
if (event.cancelable) event.preventDefault();
|
if (event.cancelable) event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pullDistance.value > SCROLL_STOP) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD;
|
isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
|
|
||||||
const isTouchSupported = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0;
|
const isTouchSupported = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0;
|
||||||
|
@ -16,3 +17,6 @@ if (isTouchSupported && !isTouchUsing) {
|
||||||
isTouchUsing = true;
|
isTouchUsing = true;
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** (MkHorizontalSwipe) 横スワイプ中か? */
|
||||||
|
export const isHorizontalSwipeSwiping = ref(false);
|
||||||
|
|
Loading…
Reference in a new issue