You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击轮播图内链接误触发swipe事件的问题求助

问题:点击轮播内的Nuxt链接同时触发Swipe与Click事件

我在用组件库的轮播组件,它通过挂载时调用工具函数initSwipeEvents监听滑动事件。手机端测试时发现,点击轮播图里的<nuxt-link>链接时,会同时触发swipe事件和click事件,导致轮播切换的同时链接跳转,影响体验。

相关代码

滑动事件工具函数 initSwipeEvents

function initSwipeEvents(el, deltaMin = 80) {
    const swipeData = {
        startX: 0,
        startY: 0,
        endX: 0,
        endY: 0,
    }
    let directionEvents = []
    el.addEventListener("touchstart", (e) => {
        const t = e.touches[0]
        swipeData.startX = t.screenX
        swipeData.startY = t.screenY
    })
    el.addEventListener("touchmove", (e) => {
        const t = e.touches[0]
        swipeData.endX = t.screenX
        swipeData.endY = t.screenY
    })
    el.addEventListener("touchend", () => {
        const deltaX = swipeData.endX - swipeData.startX
        const deltaY = swipeData.endY - swipeData.startY

        if (Math.abs(deltaX) > deltaMin) {
            if (deltaX > 0) directionEvents.push("right")
            else directionEvents.push("left")
        }
        if (Math.abs(deltaY) > deltaMin) {
            if (deltaY > 0) directionEvents.push("down")
            else directionEvents.push("up")
        }

        directionEvents.forEach((direction) =>
            el.dispatchEvent(new Event(`swipe-${direction}`))
        )

        directionEvents = []
    })
}

export default initSwipeEvents

轮播组件代码

<template>
  <div
        class="slideshow"
        tabindex="0"
        @swipe-down="goToPrev"
        @swipe-up="goToNext"
    >
            <div
                v-for="(slide, i) in slides"
                v-show="activeSlideLogic(i, internalIdx)"
                class="slide"
                :key="i"
            >
                <slot
                    :slide="slide"
                    name="slide"
                />
            </div>
    </div>
</template>
<script>
import initSwipeEvents from './path-to-initSwipeEvents'
import _clamp from 'lodash/clamp'

export default {
  props: {
    slides: {
      type: Array,
      required: true
    },
    swipeEvents: {
      type: Boolean,
      default: true
    },
    wrap: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      internalIdx: 0
    }
  },
  mounted() {   
    if (this.swipeEvents) initSwipeEvents(this.$el)
  },
  methods:{
    getLoopedIdx(idx) {
      if (this.wrap)
          return (idx + this.slides.length) % this.slides.length
      else return _clamp(idx, 0, this.slides.length - 1)
    },
    goToNext() {
      this.$nextTick(
          () =>
              (this.internalIdx = this.getLoopedIdx(this.internalIdx + 1))
      )
    },
    goToPrev() {
      this.$nextTick(
          () =>
              (this.internalIdx = this.getLoopedIdx(this.internalIdx - 1))
      )
    },
    activeSlideLogic(i, internalIdx) {
      return i === internalIdx
    }
  }
}
</script>

幻灯片插槽组件代码

<template>
  <div class="slide-video">
        <wp-image 
            class="image"
            :image="image"
        >
                <nuxt-link 
                    class="title-meta"
                    :to="to"
                >
                    <div class="title-wrapper">
                        <svg-play class="svg-play" />
                        <h2
                            class="title"
                            v-html="title"
                        />
                    </div>
                </nuxt-link>
        </wp-image>
    </div>
</template>
<script>
export default {
  props: {
    image: {
      type: Object,
      required: true
    },
    to: {
      type: String,
      required: true
    },
    title: {
      type: String,
      required: true
    }
  }
}
</script>

已尝试的无效解决方案

  • 在touchstart和touchmove事件中判断目标元素是否为.title-meta或其子元素,调用e.preventDefault();
  • 为链接添加CSS属性touch-action: none;

另外发现:修改轮播组件的goToNext和goToPrev方法打印事件对象,无论点击哪里,target始终是div.slideshow,而监听wheel事件时能得到正确目标,怀疑是工具函数绑定轮播容器$el导致事件作用域异常。

解决方案

核心思路

在触摸操作的起始阶段记录目标元素,结束时判断目标是否为可点击链接,若是则跳过swipe事件触发逻辑。

修改工具函数 initSwipeEvents

function initSwipeEvents(el, deltaMin = 80) {
    const swipeData = {
        startX: 0,
        startY: 0,
        endX: 0,
        endY: 0,
        target: null // 新增:记录触摸起始目标
    }
    let directionEvents = []

    // 递归判断元素是否为可点击链接或其子元素
    const isClickableLink = (element) => {
        let current = element
        while (current) {
            // 匹配NuxtLink渲染的a标签或自定义类名
            if (current.tagName === 'A' || current.classList.contains('title-meta')) {
                return true
            }
            current = current.parentElement
        }
        return false
    }

    el.addEventListener("touchstart", (e) => {
        const t = e.touches[0]
        swipeData.startX = t.screenX
        swipeData.startY = t.screenY
        swipeData.target = t.target // 记录触摸起始元素
    })

    el.addEventListener("touchmove", (e) => {
        const t = e.touches[0]
        swipeData.endX = t.screenX
        swipeData.endY = t.screenY
    })

    el.addEventListener("touchend", () => {
        // 若目标是可点击链接,直接跳过swipe事件触发
        if (isClickableLink(swipeData.target)) {
            directionEvents = []
            swipeData.target = null
            return
        }

        const deltaX = swipeData.endX - swipeData.startX
        const deltaY = swipeData.endY - swipeData.startY

        if (Math.abs(deltaX) > deltaMin) {
            if (deltaX > 0) directionEvents.push("right")
            else directionEvents.push("left")
        }
        if (Math.abs(deltaY) > deltaMin) {
            if (deltaY > 0) directionEvents.push("down")
            else directionEvents.push("up")
        }

        directionEvents.forEach((direction) =>
            el.dispatchEvent(new Event(`swipe-${direction}`))
        )

        directionEvents = []
        swipeData.target = null // 重置目标元素
    })
}

export default initSwipeEvents

说明

  1. 新增isClickableLink函数,递归检查触摸目标是否为<a>标签(NuxtLink最终渲染产物)或带有.title-meta类的元素;
  2. 在touchstart阶段记录触摸起始目标,避免事件冒泡导致的目标偏差;
  3. 在touchend阶段先判断目标类型,若为可点击链接则直接终止swipe事件流程;
  4. 每次触摸结束后重置目标元素,避免影响下一次操作。

之前的e.preventDefault()无效,是因为触摸事件绑定在父容器上,事件冒泡导致无法阻止父容器的事件处理,而通过在touchend阶段判断目标并跳过触发逻辑,能从根源上避免swipe事件被触发。

内容的提问来源于stack exchange,提问作者Svebor Sorić

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 00:15:33