donmbelembe / vue-dragscroll

A vue directive to make a scrollable element scroll by draging to the scroll direction
https://vue-dragscroll.clebinfosys.com/
MIT License
258 stars 32 forks source link

Prevent click event after dragging #61

Open Tofandel opened 4 years ago

Tofandel commented 4 years ago

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 commented 4 years ago

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:

zeroinformatique commented 4 years ago
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 commented 4 years ago

Please let me know the version you are using

On Mon, Feb 10, 2020, 5:35 AM OzoneGrif notifications@github.com wrote:

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; } }

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()"

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/donmbelembe/vue-dragscroll/issues/61?email_source=notifications&email_token=ACP46PLKI4PZES5MQHUWUV3RCDKP5A5CNFSM4KLIY64KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOELHGIKI#issuecomment-583951401, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACP46PLUQN7FPG42MQC2M3LRCDKP5ANCNFSM4KLIY64A .

zeroinformatique commented 4 years ago

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

donmbelembe commented 4 years ago

Please try 2.0 and let me know because your issue will be fixed on version 2

On Mon, Feb 10, 2020, 5:45 AM OzoneGrif notifications@github.com wrote:

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

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/donmbelembe/vue-dragscroll/issues/61?email_source=notifications&email_token=ACP46PO5AZUGL2EYLXK4GODRCDLVDA5CNFSM4KLIY64KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOELHGVYQ#issuecomment-583953122, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACP46PKR2UK6E726H73XDZ3RCDLVDANCNFSM4KLIY64A .

zeroinformatique commented 4 years ago

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

jos- commented 4 years ago

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 commented 4 years ago

thank you! let me try your solution

donmbelembe commented 4 years ago

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

donmbelembe commented 4 years ago

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

zeroinformatique commented 4 years ago

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 commented 4 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 9 months ago

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.