点击轮播图内链接误触发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
说明
- 新增
isClickableLink函数,递归检查触摸目标是否为<a>标签(NuxtLink最终渲染产物)或带有.title-meta类的元素; - 在
touchstart阶段记录触摸起始目标,避免事件冒泡导致的目标偏差; - 在
touchend阶段先判断目标类型,若为可点击链接则直接终止swipe事件流程; - 每次触摸结束后重置目标元素,避免影响下一次操作。
之前的e.preventDefault()无效,是因为触摸事件绑定在父容器上,事件冒泡导致无法阻止父容器的事件处理,而通过在touchend阶段判断目标并跳过触发逻辑,能从根源上避免swipe事件被触发。
内容的提问来源于stack exchange,提问作者Svebor Sorić
相关产品推荐
相关产品推荐

