滑动重构
为了让useSwipe
使用的变量统一,必须要在开始的时候就定义好传入类型,不然屎山就会在无形中不断堆积。由于手动滑动页面的时候会带动原始的页面滑动,需要阻止默认事件,于是添加了自定义事件。
tsx
import { computed, onMounted, onUnmounted, ref, Ref } from "vue";
type Point = {
x: number;
y: number;
}
interface Options {
beforeStart?: (e: TouchEvent) => void;
afterStart?: (e: TouchEvent) => void;
beforeMove?: (e: TouchEvent) => void;
afterMove?: (e: TouchEvent) => void;
beforeEnd?: (e: TouchEvent) => void;
afterEnd?: (e: TouchEvent) => void;
}
export const useSwipe = (element: Ref<HTMLElement | undefined>, options: Options) => {
const start = ref<Point>()
const end = ref<Point>()
const swiping = ref(false)
const distance = computed(() => {
if (!start.value || !end.value) { return null }
return {
x: end.value.x - start.value.x,
y: end.value.y - start.value.y,
}
})
const direction = computed(() => {
if (!distance.value) { return '' }
const { x, y } = distance.value
if (Math.abs(x) > Math.abs(y)) {
return x > 0 ? 'right' : 'left'
} else {
return y > 0 ? 'down' : 'up'
}
})
const onStart = (e: TouchEvent) => {
options?.beforeStart?.(e)
swiping.value = true
end.value = start.value = { x: e.touches[0].screenX, y: e.touches[0].screenY }
options?.afterStart?.(e)
}
const onMove = (e: TouchEvent) => {
options?.beforeMove?.(e)
if (!start.value) { return }
end.value = { x: e.touches[0].screenX, y: e.touches[0].screenY }
options?.afterMove?.(e)
}
const onEnd = (e: TouchEvent) => {
options?.beforeEnd?.(e)
swiping.value = false
options?.afterEnd?.(e)
}
onMounted(() => {
if (!element.value) { return }
element.value.addEventListener('touchstart', onStart)
element.value.addEventListener('touchmove', onMove)
element.value.addEventListener('touchend', onEnd)
})
onUnmounted(() => {
if (!element.value) { return }
element.value.removeEventListener('touchstart', onStart)
element.value.removeEventListener('touchmove', onMove)
element.value.removeEventListener('touchend', onEnd)
})
return {
swiping,
direction,
distance
}
}
由于滑动的时候过快,需要进行节流,于是手写了一个节流方法,也可以使用第三方库(lodash,underscore)
安装如下:pnpm i lodash @types/lodash
,pnpm i underscore @types/underscore
tsx
export const throttle = <T extends ((...args: unknown[])=> any)>(fn: Function,time: number) => {
let timer: number | undefined = undefined
let result: ReturnType<T>
return (...args: Parameters<T>) => {
if(timer) {
return result
} else {
result = fn(...args)
timer = setTimeout(() => {
timer = undefined
}, time)
return result
}
}
}
然后在Welcome.tsx
文件中使用,并根据路由名称来切换路由路径(并防止往后翻页使用replace
代替了push
)。
tsx
import { defineComponent, ref, Transition, VNode, watchEffect } from 'vue'
import { RouteLocationNormalizedLoaded, RouterView, useRoute, useRouter } from 'vue-router'
import s from './Welcome.module.scss'
import { useSwipe } from '../hooks/useSwipe'
import { throttle } from '../shared/throttle'
const pushMap: Record<string, string> = {
'Welcome1': '/welcome/2',
'Welcome2': '/welcome/3',
'Welcome3': '/welcome/4',
'Welcome4': '/start',
}
export const Welcome = defineComponent({
setup: (props, context) => {
const main = ref<HTMLElement>()
const { direction, swiping } = useSwipe(main, { beforeStart: e => e.preventDefault() })
const route = useRoute()
const router = useRouter()
const replace = throttle(() => {
const name = (route.name || 'Welcome1').toString()
router.replace(pushMap[name])
}, 500)
watchEffect(() => {
if (swiping.value && direction.value === 'left') {
replace()
}
})
return () =>
<div class={s.wrapper}>
<header>
<svg>
<use xlinkHref='#mangosteen' />
</svg>
<h1>山竹记账</h1>
</header>
<main class={s.main} ref={main}>
<RouterView name="main">
{({ Component: Content, route: R }: { Component: VNode, route: RouteLocationNormalizedLoaded }) =>
<Transition enterFromClass={s.slide_fade_enter_from} enterActiveClass={s.slide_fade_enter_active}
leaveToClass={s.slide_fade_leave_to} leaveActiveClass={s.slide_fade_leave_active}>
{Content}
</Transition>
}
</RouterView>
</main>
<footer>
<RouterView name="footer" />
</footer>
</div>
}
})