Skip to content

滑动重构

为了让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/lodashpnpm 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>
	}
})