Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent click event after dragging #61

Open
Tofandel opened this issue Jan 24, 2020 · 15 comments
Open

Prevent click event after dragging #61

Tofandel opened this issue Jan 24, 2020 · 15 comments

Comments

@Tofandel
Copy link

Tofandel commented Jan 24, 2020

After a drag occurs, the click event should be stopped from firing with stopImmediatePropagation

It otherwise leads to unexpected clicked elements within the scroll area

@zeroinformatique
Copy link

Same issue here, with an additional note:

On Android/Chrome, the dragscrollstart event is triggered almost instantly, making it difficult to guess if we're on a click (touch) or a real drag.

I had to use a trick to bypass this issue:

  • dragscrollstart: Starts a 100ms timer, this timer sets a variable: dragging = true
  • dragscrollend: Cleans the timer (in case it was still running, which means we're on a click)
  • click.capture: if dragging is true, preventDefault (blocks the click after a drag)

@zeroinformatique
Copy link

zeroinformatique commented Feb 10, 2020

export default class DragScrollClickFix {

  readonly DRAG_DELAY = 100; // This is the minimal delay to consider a click to be a drag, mostly usefull for touch devices

  timer: NodeJS.Timeout | null = null;
  dragging: boolean = false;

  onDragScrollStart() {
    this.timer = setTimeout(() => this.onTimer(), this.DRAG_DELAY);
  }

  onTimer() {
    this.timer = null;
    this.dragging = true;
  }

  onDragScrollEnd() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
    setTimeout(() => this.dragging = false);
  }

  onClickCapture(e: MouseEvent) {
    if (this.dragging) {
      this.dragging = false;
      e.preventDefault();
      e.stopPropagation();
    }
  }
}

In your JS:

dragScrollClickFix = new DragScrollClickFix();

Then in your Vue template:

@click.capture="e => dragScrollClickFix.onClickCapture(e)"
@dragscrollstart="dragScrollClickFix.onDragScrollStart()"
@dragscrollend="dragScrollClickFix.onDragScrollEnd()"

@donmbelembe
Copy link
Owner

donmbelembe commented Feb 10, 2020 via email

@zeroinformatique
Copy link

v1.10.2 here.
Didn't test it on 2.0.0.

@donmbelembe
Copy link
Owner

donmbelembe commented Feb 10, 2020 via email

@zeroinformatique
Copy link

zeroinformatique commented Feb 10, 2020

Just tested, issue is still present in v2.0.0
Edit: and my fix still works.

@jos-
Copy link

jos- commented Feb 17, 2020

Another possible fix is to do preventDefault() if the position of the mouse release is significantly far away (e.g. at least 5 pixels) from the start position. This is the solution https://github.com/ilyashubin/scrollbooster uses.

Somewhat shorter version of @OzoneGrif's fix:
<div v-dragscroll @dragscrollstart="onDragStart" @click.capture="onDragClick"></div>

import { dragscroll } from 'vue-dragscroll';

export default {
  directives: {
    dragscroll,
  },
  data: () => ({
    dragged: false,
    dragTimeout: null,
  }),
  methods: {
    onDragStart() {
      clearTimeout(this.dragTimeout);

      this.dragged = false;
      this.dragTimeout = setTimeout(() => { this.dragged = true; }, 100); // Minimal delay to be regarded as drag instead of click
    },
    onDragClick(e) {
      if (this.dragged) {
        e.preventDefault();
      }

      this.dragged = false;
    },
  },
};

It would be nice if one of these solutions (time comparison or distance comparison) would be implemented by default for links.

@donmbelembe
Copy link
Owner

thank you! let me try your solution

@donmbelembe
Copy link
Owner

I'm not able to get the same issue as you guys, someone can make a codepen that has exactly the issue ?

@donmbelembe
Copy link
Owner

@Tofandel and @OzoneGrif are you listening of the click event directly from the dragscroll element or on a child ?

@zeroinformatique
Copy link

zeroinformatique commented Feb 19, 2020

Child. You dragscroll the parent, using your mouse or touchscreen. When you release the drag, it triggers the click on the child component which is beneath the cursor or finger.

@drewbaker
Copy link

We use this for a thumbtray of nuxt-links below a video. When you click to drag the tray, on mouseup it will follow the link that you cursor is on.

@arjan-vdw
Copy link

arjan-vdw commented Feb 18, 2022

This worked for me:

On the anchor:

:class="{'no-pointer-event': dragged}"
State:

data(){ return { dragged: false } }

Methods:
`onDragsStart() {
clearTimeout(this.dragTimeout);

this.dragged = false;
this.dragTimeout = setTimeout(() => { this.dragged = true; }, 100);
},
onDragClick() {
setTimeout(() => { this.dragged = false; }, 100);
},`

Style:
<style lang="scss"> .no-pointer-event{ pointer-events: none; } </style>

@arildm
Copy link

arildm commented Mar 3, 2022

I made a thin component Dragscroll.vue out of it (Vue 3):

<script setup>
// Avoid emitting click event when scrolldragging
let dragging = false;
let timer = null;

function start() {
  timer = setTimeout(() => (dragging = true), 100);
}

function end() {
  clearTimeout(timer);
  setTimeout(() => (dragging = false));
}

function click(event) {
  if (dragging) {
    event.stopPropagation();
  }
}
</script>

<template>
  <div
    v-dragscroll
    @dragscrollstart="start"
    @dragscrollend="end"
    @click.capture="click"
  >
    <slot />
  </div>
</template>

@tobijafischer
Copy link

tobijafischer commented Dec 6, 2023

I made a thin component Dragscroll.vue out of it (Vue 3):

Just adding this for anyone who uses Typescript.
Using ReturnType because setTimeout typing depends on the environment (see here)

<script setup lang="ts">
// Avoid emitting click event when scrolldragging
let dragging = false;
let timer: ReturnType<typeof setTimeout> | null = null;

function start() {
  timer = setTimeout(() => (dragging = true), 100);
}

function end() {
  if (timer) {
    clearTimeout(timer);
  }
  setTimeout(() => (dragging = false));
}

function click(event: MouseEvent) {
  if (!dragging) return;
  event.stopPropagation();
}
</script>

<template>
  <div
    v-dragscroll
    @dragscrollstart="start"
    @dragscrollend="end"
    @click.capture="click"
  >
    <slot />
  </div>
</template>

Let me know I the above code can be improved. I'm no TS expert by any means.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants