Jak odróżnić „kliknięcie” i „przeciągnięcie” myszą

165

Używam jQuery.clickobsłużyć zdarzenie kliknięcia myszki na wykresie Raphael tymczasem muszę obsłużyć myszy dragzdarzenie, przeciągnij mysz składa mousedown, mouseupa mousemovew Raphael.

Jest trudny do rozróżnienia, clicka dragponieważ clickzawiera również mousedown& mouseup, Jak odróżnić „kliknięcie” i „przeciągnięcie” myszą niż w JavaScript?

Leem
źródło

Odpowiedzi:

192

Myślę, że różnica polega na tym, że istnieje mousemovemiędzy mousedowni mouseupw przeciągnięciu, ale nie w kliknięciu.

Możesz zrobić coś takiego:

const element = document.createElement('div')
element.innerHTML = 'test'
document.body.appendChild(element)
let moved
let downListener = () => {
    moved = false
}
element.addEventListener('mousedown', downListener)
let moveListener = () => {
    moved = true
}
element.addEventListener('mousemove', moveListener)
let upListener = () => {
    if (moved) {
        console.log('moved')
    } else {
        console.log('not moved')
    }
}
element.addEventListener('mouseup', upListener)

// release memory
element.removeEventListener('mousedown', downListener)
element.removeEventListener('mousemove', moveListener)
element.removeEventListener('mouseup', upListener)
wong2
źródło
38
Pamiętaj tylko, aby wymagać minimalnego delta X lub Y na ruchu myszy, aby wywołać przeciągnięcie. Byłoby frustrujące,
gdybyśmy
12
Myślę, że to już nie działa w najnowszym chrome: 32.0.1700.72 Mousemove odpala niezależnie od tego, czy poruszasz myszą, czy nie
mrjrdnthms
17
Ten zaakceptowany kod odpowiedzi powinien zawierać warunek minimalnej delta między współrzędnymi myszy XY w miejscu mousedowni mouseupzamiast nasłuchiwać mousemovezdarzenia w celu ustawienia flagi. Ponadto, byłoby rozwiązać ten problem, o którym wspomina @mrjrdnthms
Billybobbonnet
2
Używam przeglądarki Chrome 56.0.2924.87 (64-bitowej) i nie występują problemy, które opisuje @mrjrdnthms.
jkupczak
1
@AmerllicA to prawdopodobnie nie jest błąd, ale oczekiwane zachowanie, jednak możesz obserwować zdarzenia mouseenter i mouseleave, jeśli jest to interesujące dla twojego przypadku użycia
Rivenfall
37

Jeśli korzystasz już z jQuery:

var $body = $('body');
$body.on('mousedown', function (evt) {
  $body.on('mouseup mousemove', function handler(evt) {
    if (evt.type === 'mouseup') {
      // click
    } else {
      // drag
    }
    $body.off('mouseup mousemove', handler);
  });
});
Gustavo Rodrigues
źródło
Nawet jeśli nieznacznie poruszysz myszą podczas klikania, to powie drag. Może być potrzebny dodatkowy zakres, jak mówią inne komentarze.
ChiMo
@ChiMo To, czego używam, to zapisywanie pozycji myszy od pierwszej evti porównywanie z pozycją drugiej evt, czyli np if (evt.type === 'mouseup' || Math.abs(evt1.pageX - evt2.pageX) < 5 || Math.abs(evt1.pageY - evt2.pageY) < 5) { .... : .
Gustavo Rodrigues
1
Wypróbowałem wszystkie inne odpowiedzi na to pytanie i jest to jedyna, która zadziałała podczas sprawdzania .on('mouseup mousemove touchend touchmove'), a na dodatek nie ustawia zmiennych pozycji. Świetne rozwiązanie!
TheThirdMan
Czasami, gdy kliknąłem na element, „evt.type” zwraca „mousemove” zamiast na myszy. Jak mogę rozwiązać ten problem?
Libu Mathew
27

Środek czyszczący ES2015

let drag = false;

document.addEventListener('mousedown', () => drag = false);
document.addEventListener('mousemove', () => drag = true);
document.addEventListener('mouseup', () => console.log(drag ? 'drag' : 'click'));

Nie wystąpiły żadne błędy, jak komentują inni.

Przemek
źródło
6
Cierpi na kliknięcia z drobnymi ruchami.
Amir Keibi
1
@AmirKeibi, możesz policzyć ruchy myszy (lub nawet obliczyć odległość między dwoma kliknięciami, ale to byłaby przesada)
Rivenfall
19

To powinno działać dobrze. Podobna do zaakceptowanej odpowiedzi (choć przy użyciu jQuery), ale isDraggingflaga jest resetowana tylko wtedy, gdy nowa pozycja myszy różni się od tej w mousedownzdarzeniu. W przeciwieństwie do zaakceptowanej odpowiedzi, która działa na najnowszych wersjach Chrome, gdzie mousemovejest uruchamiana niezależnie od tego, czy mysz została przeniesiona, czy nie.

var isDragging = false;
var startingPos = [];
$(".selector")
    .mousedown(function (evt) {
        isDragging = false;
        startingPos = [evt.pageX, evt.pageY];
    })
    .mousemove(function (evt) {
        if (!(evt.pageX === startingPos[0] && evt.pageY === startingPos[1])) {
            isDragging = true;
        }
    })
    .mouseup(function () {
        if (isDragging) {
            console.log("Drag");
        } else {
            console.log("Click");
        }
        isDragging = false;
        startingPos = [];
    });

Możesz również dostosować sprawdzanie współrzędnych, mousemovejeśli chcesz dodać trochę tolerancji (tj. Traktuj drobne ruchy jako kliknięcia, a nie przeciąganie).

nirvana-msu
źródło
12

Jeśli masz ochotę użyć Rxjs :

var element = document;

Rx.Observable
  .merge(
    Rx.Observable.fromEvent(element, 'mousedown').mapTo(0),
    Rx.Observable.fromEvent(element, 'mousemove').mapTo(1)
  )
  .sample(Rx.Observable.fromEvent(element, 'mouseup'))
  .subscribe(flag => {
      console.clear();
      console.log(flag ? "drag" : "click");
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/Rx.js"></script>

Jest to bezpośredni klon tego, co @ wong2 zrobił w swojej odpowiedzi, ale przekonwertowany na RxJs.

Ciekawe użycie sample. sampleOperator weźmie ostatnią wartość z Źródła ( mergez mousedowna mousemove) i wydzielają je, kiedy wewnętrzna zaobserwować ( mouseup) wytwarza.

Dorus
źródło
22
Cały mój kod piszę za pomocą obserwabli, aby mój szef nie mógł zatrudnić kogoś innego, kto mnie zastąpi.
Reactgular
11

Jak mrjrdnthms wskazuje w swoim komentarzu do zaakceptowanej odpowiedzi, to już nie działa w Chrome (zawsze uruchamia ruch myszy), dostosowałem odpowiedź Gustavo (ponieważ używam jQuery), aby rozwiązać problem zachowania Chrome.

var currentPos = [];

$(document).on('mousedown', function (evt) {

   currentPos = [evt.pageX, evt.pageY]

  $(document).on('mousemove', function handler(evt) {

    currentPos=[evt.pageX, evt.pageY];
    $(document).off('mousemove', handler);

  });

  $(document).on('mouseup', function handler(evt) {

    if([evt.pageX, evt.pageY].equals(currentPos))
      console.log("Click")
    else
      console.log("Drag")

    $(document).off('mouseup', handler);

  });

});

Array.prototype.equalsFunkcja pochodzi z tej odpowiedzi

Francisco Aquino
źródło
1
To prawie zadziałało, ale otrzymałem błąd z [evt.pageX, evt.pageY].equals()polecenia. Zastąpiłem to (evt.pageX === currentPos[0] && evt.pageY===currentPos[1])i wszystko było dobrze. :)
user2441511
Na equalspotrzeby należy dodać kod z linku na dole mojego postu
Francisco Aquino
Ach, to wszystko wyjaśnia. Dzięki.
user2441511
1
Wydaje się, że nie rozumiem logiki. Dlaczego aktualizować currentPosna mousemove? Czy to nie oznacza, że ​​traktujesz niektóre przeciągnięcia jako kliknięcia?
nirvana-msu
1
To się nie uruchamia, jeśli "mouseup"nadal poruszasz myszą.
ChiMo,
9

Wszystkie te rozwiązania albo psują się przy niewielkich ruchach myszy, albo są nadmiernie skomplikowane.

Oto proste rozwiązanie, które można dostosować, wykorzystujące dwa detektory zdarzeń. Delta to odległość w pikselach, którą należy przesunąć w poziomie lub w pionie między zdarzeniami w górę iw dół, aby kod zaklasyfikował go jako przeciągnięcie, a nie kliknięcie. Dzieje się tak, ponieważ czasami przed podniesieniem przesuniesz mysz lub palec o kilka pikseli.

const delta = 6;
let startX;
let startY;

element.addEventListener('mousedown', function (event) {
  startX = event.pageX;
  startY = event.pageY;
});

element.addEventListener('mouseup', function (event) {
  const diffX = Math.abs(event.pageX - startX);
  const diffY = Math.abs(event.pageY - startY);

  if (diffX < delta && diffY < delta) {
    // Click!
  }
});
andreyrd
źródło
Zdecydowanie najlepsza odpowiedź!
Giorgio Tempesta
Cześć @andreyrd, czy mogę wiedzieć, co deltajest do tego używane? ma to coś wspólnego z dotknięciem w urządzeniu mobilnym?
Haziq
1
@Haziq Myślę, że tak jak ludzie wspominani w komentarzach o najlepszych rozwiązaniach deltajest używany do określenia „Byłoby frustrujące, gdybyśmy spróbowali kliknąć i wykonać operację przeciągania z powodu jednego kliknięcia myszy”
Michael Bykhovtsev
1
Zaktualizowałem odpowiedź o wyjaśnienie. Zasadniczo, jeśli Twój palec ma mniej niż 6 pikseli, nadal będzie to liczone jako kliknięcie. Jeśli przesunie się o 6 lub więcej pikseli, liczy się jako przeciągnięcie.
andreyrd
5

Używając jQuery z 5-pikselowym X / Y theshold do wykrywania przeciągnięcia:

var dragging = false;
$("body").on("mousedown", function(e) {
  var x = e.screenX;
  var y = e.screenY;
  dragging = false;
  $("body").on("mousemove", function(e) {
    if (Math.abs(x - e.screenX) > 5 || Math.abs(y - e.screenY) > 5) {
      dragging = true;
    }
  });
});
$("body").on("mouseup", function(e) {
  $("body").off("mousemove");
  console.log(dragging ? "drag" : "click");
});
srebrny Wiatr
źródło
2

Jeśli chcesz tylko odfiltrować przypadek przeciągania, zrób to w następujący sposób:

var moved = false;
$(selector)
  .mousedown(function() {moved = false;})
  .mousemove(function() {moved = true;})
  .mouseup(function(event) {
    if (!moved) {
        // clicked without moving mouse
    }
  });
jqgsninimo
źródło
1

Czysty JS z DeltaX i DeltaY

To DeltaX i DeltaY, jak sugeruje komentarz w zaakceptowanej odpowiedzi, aby uniknąć frustrującego doświadczenia podczas próby kliknięcia i zamiast tego uzyskać operację przeciągania z powodu jednego kliknięcia myszą.

    deltaX = deltaY = 2;//px
    var element = document.getElementById('divID');
    element.addEventListener("mousedown", function(e){
        if (typeof InitPageX == 'undefined' && typeof InitPageY == 'undefined') {
            InitPageX = e.pageX;
            InitPageY = e.pageY;
        }

    }, false);
    element.addEventListener("mousemove", function(e){
        if (typeof InitPageX !== 'undefined' && typeof InitPageY !== 'undefined') {
            diffX = e.pageX - InitPageX;
            diffY = e.pageY - InitPageY;
            if (    (diffX > deltaX) || (diffX < -deltaX)
                    || 
                    (diffY > deltaY) || (diffY < -deltaY)   
                    ) {
                console.log("dragging");//dragging event or function goes here.
            }
            else {
                console.log("click");//click event or moving back in delta goes here.
            }
        }
    }, false);
    element.addEventListener("mouseup", function(){
        delete InitPageX;
        delete InitPageY;
    }, false);

   element.addEventListener("click", function(){
        console.log("click");
    }, false);
Waqas Bukhary
źródło
1

W przypadku akcji publicznej na mapie OSM (umieść znacznik po kliknięciu) pytanie brzmiało: 1) jak określić czas trwania ruchu myszy w dół -> w górę (nie możesz sobie wyobrazić tworzenia nowego znacznika dla każdego kliknięcia) i 2) tak ruch myszy w dół-> w górę (tj. użytkownik przeciąga mapę).

const map = document.getElementById('map');

map.addEventListener("mousedown", position); 
map.addEventListener("mouseup", calculate);

let posX, posY, endX, endY, t1, t2, action;

function position(e) {

  posX = e.clientX;
  posY = e.clientY;
  t1 = Date.now();

}

function calculate(e) {

  endX = e.clientX;
  endY = e.clientY;
  t2 = (Date.now()-t1)/1000;
  action = 'inactive';

  if( t2 > 0.5 && t2 < 1.5) { // Fixing duration of mouse down->up

      if( Math.abs( posX-endX ) < 5 && Math.abs( posY-endY ) < 5 ) { // 5px error on mouse pos while clicking
         action = 'active';
         // --------> Do something
      }
  }
  console.log('Down = '+posX + ', ' + posY+'\nUp = '+endX + ', ' + endY+ '\nAction = '+ action);    

}
Wolden
źródło
0

Inne rozwiązanie wykorzystujące klasowy waniliowy JS wykorzystujący próg odległości

private initDetectDrag(element) {
    let clickOrigin = { x: 0, y: 0 };
    const dragDistanceThreshhold = 20;

    element.addEventListener('mousedown', (event) => {
        this.isDragged = false
        clickOrigin = { x: event.clientX, y: event.clientY };
    });
    element.addEventListener('mousemove', (event) => {
        if (Math.sqrt(Math.pow(clickOrigin.y - event.clientY, 2) + Math.pow(clickOrigin.x - event.clientX, 2)) > dragDistanceThreshhold) {
            this.isDragged = true
        }
    });
}

I dodaj do klasy (SOMESLIDER_ELEMENT może być również dokumentem globalnym):

private isDragged: boolean;
constructor() {
    this.initDetectDrag(SOMESLIDER_ELEMENT);
    this.doSomeSlideStuff(SOMESLIDER_ELEMENT);
    element.addEventListener('click', (event) => {
        if (!this.sliderIsDragged) {
            console.log('was clicked');
        } else {
            console.log('was dragged, ignore click or handle this');
        }
    }, false);
}
Tim Rasim
źródło
0

Jeśli chcesz sprawdzić zachowanie kliknięcia lub przeciągnięcia określonego elementu, możesz to zrobić bez konieczności słuchania treści.

$(document).ready(function(){
  let click;
  
  $('.owl-carousel').owlCarousel({
    items: 1
  });
  
  // prevent clicks when sliding
  $('.btn')
    .on('mousemove', function(){
      click = false;
    })
    .on('mousedown', function(){
      click = true;
    });
    
  // change mouseup listener to '.content' to listen to a wider area. (mouse drag release could happen out of the '.btn' which we have not listent to). Note that the click will trigger if '.btn' mousedown event is triggered above
  $('.btn').on('mouseup', function(){
    if(click){
      $('.result').text('clicked');
    } else {
      $('.result').text('dragged');
    }
  });
});
.content{
  position: relative;
  width: 500px;
  height: 400px;
  background: #f2f2f2;
}
.slider, .result{
  position: relative;
  width: 400px;
}
.slider{
  height: 200px;
  margin: 0 auto;
  top: 30px;
}
.btn{
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  height: 100px;
  background: #c66;
}
.result{
  height: 30px;
  top: 10px;
  text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css" />
<div class="content">
  <div class="slider">
    <div class="owl-carousel owl-theme">
      <div class="item">
        <a href="#" class="btn" draggable="true">click me without moving the mouse</a>
      </div>
      <div class="item">
        <a href="#" class="btn" draggable="true">click me without moving the mouse</a>
      </div>
    </div>
    <div class="result"></div>
  </div>
  
</div>

Lasithds
źródło
0

z odpowiedzi @Przemek,

function listenClickOnly(element, callback, threshold=10) {
  let drag = 0;
  element.addEventListener('mousedown', () => drag = 0);
  element.addEventListener('mousemove', () => drag++);
  element.addEventListener('mouseup', e => {
    if (drag<threshold) callback(e);
  });
}

listenClickOnly(
  document,
  () => console.log('click'),
  10
);

Jehong Ahn
źródło