实现左滑列表删除 Vue+TSX

157 阅读1分钟

image.png

1. 代码

type Point = {
  x: number
  y: number
}
export const useSlide = (element: Ref<HTMLElement | undefined>) => {
  const start = ref<Point>()
  const end = ref<Point>()
  const swiping = ref(false)
  const LiIndex = ref<number>()
  const currentTag = ref<HTMLOListElement>() // 监听父元素
  const startPointed = ref<any>()
  const endPointed = ref<any>()
  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 || !currentTag.value || pressTime < 1) { //点击start和end的间隔小于300ms 返回click
      return ''
    }
    const { x, y } = distance.value
    const LiList = Array.from(currentTag.value!.children)
    for (let index = 0; index < LiList.length; index++) {
        const li = LiList[index]
        if (li.contains(startPointed.value) && li.contains(endPointed.value)) { // 判定滑动位置为当前li
          if (Math.abs(x) > Math.abs(y) && Math.abs(x) > 20) { // 判断滑动方向与距离
            if(x<0){LiIndex.value = index; return 'left'} 
            else if(x>0 && index === LiIndex.value){return 'right'}
          } else {
            return ''
          }
        }
      }
  })
  let pressTime = 0
  let pressTimer = 0
  const onStart = (e: TouchEvent) => {
    e.preventDefault()
    pressTime = 0
    currentTag.value = e.currentTarget as HTMLOListElement
    pressTimer = setInterval(() => {
        pressTime = pressTime + 1
      },300)
    startPointed.value = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY)
    end.value = start.value = { x: e.touches[0].screenX, y: e.touches[0].screenY }
  }
  const onMove = (e: TouchEvent) => {
    if (!start.value) {
      return
    }
    swiping.value = true
    end.value = { x: e.touches[0].screenX, y: e.touches[0].screenY }
    endPointed.value = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY)
  }
  const onEnd = (e: TouchEvent) => {
    e.preventDefault()
    clearInterval(pressTimer)
    const target = e.target as HTMLElement
    if(pressTime<1 && target.parentNode?.nodeName.toLowerCase() === 'li'){target.click()}  //手动触发click
    swiping.value = false
  } 

  watch(element, () => { // 这里没有使用onMounted 因为当前组件挂载时 element还不存在
    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 {
    LiIndex, direction
  }
}

2. key points

  • click和touch事件同时存在时会先触发touch,再触发click,所以当click时 通过点击的开始和结束的间隔来区分
  • 为了不让用户在滑动时触发click,使用e.preventDefault(),但为了能让用户在点击时触发click,需要在touchEnd中判断区分点击事件 并手动触发点击元素的click事件