Как привязать события "touchstart" и "click", но не реагировать на оба?

Я работаю на мобильный веб-сайт, который должен работать на различных устройствах. У меня сейчас голова болит, это ежевика.

нам нужно поддерживать как щелчки клавиатуры, так и сенсорные события.

в идеале я бы просто использовать:

$thing.click(function(){...})

но проблема, с которой мы сталкиваемся, заключается в том, что некоторые из этих устройств blackberry имеют очень раздражающую задержку с момента прикосновения к нему, вызывая щелчок.

средство использовать как touchstart:

$thing.bind('touchstart', function(event){...})

но как я могу связать оба события,но только один? Мне все еще нужно событие click для клавиатурных устройств, но, конечно, не хочу, чтобы событие click срабатывало, если я использую сенсорное устройство.

бонусный вопрос: есть ли в любом случае, чтобы сделать это и дополнительно разместить браузеры, которые даже не имеют события touchstart? При исследовании этого, похоже, BlackBerry OS5 не поддерживает touchstart, поэтому также нужно будет полагаться на события click для этот браузер.

дополнение:

возможно, более полный вопрос:

С помощью jQuery можно / рекомендуется обрабатывать как сенсорные взаимодействия, так и взаимодействия с мышью с одинаковыми привязками?

в идеале, ответ да. Если нет, у меня есть несколько вариантов:

1) Мы используем WURFL для получения информации об устройстве, чтобы создать собственную матрицу устройств. В зависимости от устройства мы будем использовать touchstart или click.

2) определять сенсорная поддержка в браузере через JS (мне нужно сделать еще несколько исследований по этому вопросу, но кажется, что это выполнимо).

однако, это все еще оставляет одну проблему: как насчет устройств, которые поддерживают оба. Некоторые из телефонов, которые мы поддерживаем (а именно Nokias и ежевика) имеют оба сенсорных экрана и клавиатуры. Таким образом, это возвращает меня к первоначальному вопросу...есть ли способ разрешить оба сразу?

30 ответов


обновление: Проверьте jQuery Указатель Событий Polyfill проект, который позволяет привязывать к событиям "указатель" вместо выбора между мышью и касанием.


привязка к обоим, но сделайте флаг, чтобы функция срабатывала только один раз на 100 мс или около того.

var flag = false;
$thing.bind('touchstart click', function(){
  if (!flag) {
    flag = true;
    setTimeout(function(){ flag = false; }, 100);
    // do something
  }

  return false
});

это исправление, которое я "создаю", и оно вынимает GhostClick и реализует FastClick. Попробуйте самостоятельно и дайте нам знать, если это сработало для вас.

$(document).on('touchstart click', '.myBtn', function(event){
        if(event.handled === false) return
        event.stopPropagation();
        event.preventDefault();
        event.handled = true;

        // Do your magic here

});

вы можете попробовать что-то вроде этого:

var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);

обычно это также работает:

$('#buttonId').on('touchstart click', function(e){
    e.stopPropagation(); e.preventDefault();
    //your code here

});

просто добавив return false; в конце on("click touchstart") функция события может разрешить эту проблему.

$(this).on("click touchstart", function() {
  // Do things
  return false;
});

из документации jQuery на .on ()

возвращение false из обработчика событий будет автоматически вызываться event.stopPropagation() и event.preventDefault(). А false значение также может быть передано для обработчика как сокращение для function(){ return false; }.


Я должен был сделать что-то подобное. Вот упрощенная версия того, что сработало для меня. Если обнаружено событие касания, удалите привязку щелчка.

$thing.on('touchstart click', function(event){
  if (event.type == "touchstart")
    $(this).off('click');

  //your code here
});

в моем случае событие click было привязано к <a> элемент, поэтому мне пришлось удалить привязку щелчка и повторно связать событие щелчка, которое предотвратило действие по умолчанию для <a> элемент.

$thing.on('touchstart click', function(event){
  if (event.type == "touchstart")
    $(this).off('click').on('click', function(e){ e.preventDefault(); });

  //your code here
});

мне удалось следующим образом.

Легкий Peasy...

$(this).on('touchstart click', function(e){
  e.preventDefault();
  //do your stuff here
});

обычно вы не хотите смешивать API touch и non-touch (click) по умолчанию. Как только вы перейдете в мир прикосновения, вам будет проще иметь дело только с функциями, связанными с прикосновением. Ниже приведены некоторые псевдо-код, который будет делать то, что вы хотите.

Если вы подключаетесь в событии touchmove и отслеживаете местоположения, вы можете добавить больше элементов в функцию doTouchLogic для обнаружения жестов и прочего.

var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;

$thing.bind('touchstart'), function() {
     var d = new Date();
     touchStartTime = d.getTime();
     touchStartLocation = mouse.location(x,y);
});

$thing.bind('touchend'), function() {
     var d = new Date();
     touchEndTime= d.getTime();
     touchEndLocation= mouse.location(x,y);
     doTouchLogic();
});

function doTouchLogic() {
     var distance = touchEndLocation - touchStartLocation;
     var duration = touchEndTime - touchStartTime;

     if (duration <= 100ms && distance <= 10px) {
          // Person tapped their finger (do click/tap stuff here)
     }
     if (duration > 100ms && distance <= 10px) {
          // Person pressed their finger (not a quick tap)
     }
     if (duration <= 100ms && distance > 10px) {
          // Person flicked their finger
     }
     if (duration > 100ms && distance > 10px) {
          // Person dragged their finger
     }
}

Я считаю, что лучше всего сейчас использовать:

$('#object').on('touchend mouseup', function () { });

touchend

событие touchend запускается, когда точка касания удаляется с сенсорной поверхности.

событие touchend будет не вызвать любые события мыши.


mouseup с

событие mouseup отправляется элементу, когда указатель мыши находится над элементом, а кнопка мыши выпущенный. Любой элемент HTML может получить это событие.

событие mouseup будет не триггер события касания.

пример

$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>

изменить (2017)

С 2017 года браузеры, начиная с Chrome и делая шаги к созданию события click .on("click") более совместимый как для мыши, так и для касания путем исключать задержку произведенную краном события по запросам щелчка.

это приводит к выводу, что возврат к использованию только события click будет самым простым решением, продвигающимся вперед.

Я еще не сделал никакого перекрестного тестирования браузера, чтобы увидеть, если это практично.


проверьте быстрые кнопки и chost кликов из google https://developers.google.com/mobile/articles/fast_buttons


хорошо... Все это очень сложно.

Если у вас есть modernizr, это без проблем.

ev = Modernizr.touch ? 'touchstart' : 'click';

$('#menu').on(ev, '[href="#open-menu"]', function(){
  //winning
});

другая реализация для лучшего обслуживания. Однако эта техника также будет делать event.это происходит (). Щелчок не пойман ни на одном другом элементе, который щелкнул для 100ms.

var clickObject = {
    flag: false,
    isAlreadyClicked: function () {
        var wasClicked = clickObject.flag;
        clickObject.flag = true;
        setTimeout(function () { clickObject.flag = false; }, 100);
        return wasClicked;
    }
};

$("#myButton").bind("click touchstart", function (event) {
   if (!clickObject.isAlreadyClicked()) {
      ...
   }
}

просто для целей документации, вот что я сделал для самого быстрого / самого отзывчивого клика на рабочем столе / нажмите на мобильное решение, которое я мог бы придумать:

Я заменил jQuery on функция с измененным, который, всякий раз, когда браузер поддерживает сенсорные события, заменил все мои события щелчка с touchstart.

$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
    on: (function(){
        var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
        return function( types, selector, data, fn, one ) {
            if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
            return this._on( types, selector, data, fn);
        };
    }()),
});

использование, чем было бы точно так же, как и раньше, например:

$('#my-button').on('click', function(){ /* ... */ });

но он будет использовать touchstart, когда доступен, нажмите, когда нет. Никаких задержек любого рода не требуется: D


Я просто придумал идею запомнить, если ontouchstart никогда не срабатывает. В этом случае мы находимся на устройстве, которое поддерживает его и хочет игнорировать onclick событие. С ontouchstart должен всегда срабатывать до onclick, Я использую этот:

<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>

можно попробовать так:

var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);

это сработало для меня, мобильный слушает оба, поэтому предотвратите одно, которое является событием касания. рабочий стол только слушать мышь.

 $btnUp.bind('touchstart mousedown',function(e){
     e.preventDefault();

     if (e.type === 'touchstart') {
         return;
     }

     var val = _step( _options.arrowStep );
               _evt('Button', [val, true]);
  });

в моем случае это сработало в совершенстве:

jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
    jQuery(this).off('mouseup');
}
});

основная проблема заключалась в том, что вместо mouseup я пытался с помощью click, на сенсорных устройствах одновременно запускались click и touchend, если я использую click off, некоторые функции вообще не работали на мобильных устройствах. Проблема с click заключается в том, что это глобальное событие, которое запускает остальную часть события, включая touchend.


Я также работаю над веб-приложением для Android/iPad, и кажется, что если только использовать "touchmove" достаточно для "перемещения компонентов" ( не нужно touchstart ). Отключив touchstart, вы можете использовать .нажмите кнопку(); в jQuery. На самом деле он работает, потому что он не перегружен touchstart.

наконец, вы можете binb .live ("touchstart", функция(e) { e.stopPropagation ();}); чтобы попросить событие touchstart прекратить распространение, living room нажать (), чтобы получить триггер.

Это работать на меня.


есть много вещей, которые следует учитывать при попытке решить эту проблему. Большинство решений либо прерывают прокрутку, либо не обрабатывают события ghost click должным образом.

для полного решения см. https://developers.google.com/mobile/articles/fast_buttons

NB: вы не можете обрабатывать события щелчка призрака на основе каждого элемента. Отложенный щелчок запускается местоположением экрана, поэтому, если события касания каким-либо образом изменяют страницу, событие щелчка будет отправлено на новый версия страницы.


это может быть эффективным, чтобы назначить события 'touchstart mousedown' или 'touchend mouseup' во избежание нежелательных побочных эффектов использования click.


воспользовавшись тем, что щелчок будет всегда следуйте за сенсорным событием, вот что я сделал, чтобы избавиться от "призрачного щелчка" без необходимости использовать таймауты или глобальные флаги.

$('#buttonId').on('touchstart click', function(event){
    if ($(this).data("already")) {
        $(this).data("already", false);
        return false;
    } else if (event.type == "touchstart") {
        $(this).data("already", true);
    }
    //your code here
});

в основном, когда ontouchstart событие срабатывает на элементе, флаг набор, а затем удаляется (и игнорируется), когда приходит щелчок.


почему бы не использовать API событий jQuery?

http://learn.jquery.com/events/event-extensions/

Я успешно использовал это простое событие. Он чистый, namespaceable и достаточно гибкой, чтобы улучшить.

var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
var eventType = isMobile ? "touchstart" : "click";

jQuery.event.special.touchclick = {
  bindType: eventType,
  delegateType: eventType
};

Если вы используете jQuery, для меня очень хорошо работало следующее:

var callback; // Initialize this to the function which needs to be called

$(target).on("click touchstart", selector, (function (func){
    var timer = 0;
    return function(e){
        if ($.now() - timer < 500) return false;
        timer = $.now();
        func(e);
    }
})(callback));

другие решения также хороши, но я связывал несколько событий в цикле и нуждался в функции самостоятельного вызова для создания соответствующего закрытия. Кроме того, я не хотел отключать привязку, так как я хотел, чтобы она могла вызываться при следующем щелчке/touchstart.

может помочь кому-то в аналогичной ситуации!


для простых функций, просто признать нажмите я использую следующий код:

var element = $("#element");

element.click(function(e)
{
  if(e.target.ontouchstart !== undefined)
  {
    console.log( "touch" );
    return;
  }
  console.log( "no touch" );
});

это вернет "touch", если определено событие touchstart и "No touch", если нет. Как я уже сказал, Это простой подход для событий click/tap.


Я пытаюсь это, и до сих пор это работает (но я только на Android/Phonegap, поэтому будьте осторожны emptor)

  function filterEvent( ob, ev ) {
      if (ev.type == "touchstart") {
          ob.off('click').on('click', function(e){ e.preventDefault(); });
      }
  }
  $('#keypad').on('touchstart click', '.number, .dot', function(event) {
      filterEvent( $('#keypad'), event );
      console.log( event.type );  // debugging only
           ... finish handling touch events...
  }

мне не нравится тот факт, что я повторно связываю обработчики на каждом касании, но все, что считается касаниями, происходит не очень часто (в компьютерное время!)

У меня есть тонна обработчиков, таких как для "#keypad", поэтому простая функция, которая позволяет мне решать проблему без слишком большого кода, - вот почему я пошел этим путем.


будучи для меня лучшим ответом, который дал Мотти, я просто пытаюсь сделать его код более многоразовым, поэтому это мой вклад:

bindBtn ("#loginbutton",loginAction);

function bindBtn(element,action){

var flag = false;
$(element).bind('touchstart click', function(e) {
    e.preventDefault();
    if (!flag) {
        flag = true;
        setTimeout(function() {
            flag = false;
        }, 100);
        // do something
        action();
    }
    return false;
});

попробуйте использовать Virtual Mouse (vmouse) Bindings С jQuery Mobile. Это виртуальное событие специально для вашего случая:

$thing.on('vclick', function(event){ ... });

http://api.jquerymobile.com/vclick/

список поддержки браузера: http://jquerymobile.com/browser-support/1.4/


EDIT: мой бывший ответ (основанный на ответах в этой теме) не был для меня способом. Я хотел, чтобы подменю развернулось на мыши enter или touch click и свернуть на мыши leave или другой сенсорный щелчок. Поскольку события мыши обычно запускаются после сенсорных событий, было сложно писать прослушиватели событий, которые поддерживают одновременно сенсорный экран и ввод мыши.

jQuery плагин: Touch Или Мыши

I в конечном итоге написал плагин jQuery под названием"Touch Или Мыши" (897 байт minified), который может определить, было ли событие вызвано сенсорным экраном или мышью (без тестирования сенсорной поддержки!). Это позволяет поддерживать как сенсорный экран и мышь одновременно и полностью отделить их события.

таким образом, OP может использовать touchstart или touchend для быстрого реагирования на касание щелкает и click за клики вызывается только by a мышь.

демонстрация

сначала нужно сделать ie. элемент тела отслеживать сенсорные события:

$(document.body).touchOrMouse('init');

события мыши наши привязаны к элементам по умолчанию и путем вызова $body.touchOrMouse('get', e) мы можем узнать, было ли событие вызвано сенсорным экраном или мышью.

$('.link').click(function(e) {
  var touchOrMouse = $(document.body).touchOrMouse('get', e);

  if (touchOrMouse === 'touch') {
    // Handle touch click.
  }
  else if (touchOrMouse === 'mouse') {
    // Handle mouse click.
  }
}

см. плагин на работе по адресуhttp://jsfiddle.net/lmeurs/uo4069nh.

объяснение

  1. этот плагин должен быть называется на IE. элемент тела для отслеживания touchstart и touchend событий, таким образом touchend событие не должно запускаться на элементе триггера(т. е. ссылка или кнопка). Между этими двумя событиями касания этот плагин считает, что любое событие мыши вызывается касанием.
  2. события мыши запускаются только после touchend, когда событие мыши происходит внутри ghostEventDelay (опция, 1000 мс по умолчанию) после touchend, этот плагин считает, что событие мыши вызывается касанием.
  3. при нажатии на элемент с помощью сенсорного экрана, элемент получает :active государство. The mouseleave событие запускается только после того, как элемент теряет это состояние ie. щелчок по другому элементу. Так как это могут быть секунды (или минуты!) после mouseenter событие было запущено, этот плагин отслеживает последний элемент mouseenter событие: если последний mouseenter событие было вызвано касанием, следующее mouseleave событие также считается вызываемым касанием.

вот простой способ сделать это:

// A very simple fast click implementation
$thing.on('click touchstart', function(e) {
  if (!$(document).data('trigger')) $(document).data('trigger', e.type);
  if (e.type===$(document).data('trigger')) {
    // Do your stuff here
  }
});

вы в основном сохраняете первый тип события, который запускается в свойство "trigger" в объекте данных jQuery, прикрепленном к корневому документу, и выполняете только тогда, когда тип события равен значению в "trigger". На сенсорных устройствах цепочка событий, скорее всего, будет "touchstart", а затем "click"; однако обработчик "click" не будет выполнен, потому что "click" не соответствует исходному типу события, сохраненному в "trigger" ("как touchstart").

предположение, и я считаю, что это безопасно, заключается в том, что ваш смартфон не будет спонтанно меняться с сенсорного устройства на устройство мыши, иначе кран никогда не будет регистрироваться, потому что тип события "триггер" сохраняется только один раз на страницу загрузки и "нажмите"никогда не будет соответствовать "touchstart".

вот codepen, с которым вы можете играть (попробуйте нажать на кнопку на сенсорном устройстве - не должно быть задержки щелчка): http://codepen.io/thdoan/pen/xVVrOZ

Я также реализовал это как простой плагин jQuery, который также поддерживает фильтрацию потомков jQuery, передавая строку селектора:

// A very simple fast click plugin
// Syntax: .fastClick([selector,] handler)
$.fn.fastClick = function(arg1, arg2) {
  var selector, handler;
  switch (typeof arg1) {
    case 'function':
      selector = null;
      handler = arg1;
      break;
    case 'string':
      selector = arg1;
      if (typeof arg2==='function') handler = arg2;
      else return;
      break;
    default:
      return;
  }
  this.on('click touchstart', selector, function(e) {
    if (!$(document).data('trigger')) $(document).data('trigger', e.type);
    if (e.type===$(document).data('trigger')) handler.apply(this, arguments);
  });
};

Codepen:http://codepen.io/thdoan/pen/GZrBdo/


лучший метод, который я нашел, - написать событие touch и вызвать это событие обычным событием click программно. Таким образом, у вас есть все ваши обычные события click, а затем вам нужно добавить только один обработчик событий для всех событий touch. Для каждого узла, который вы хотите сделать осязаемым, просто добавьте к нему класс "осязаемый", чтобы вызвать обработчик касания. С Jquery он работает так с некоторой логикой, чтобы убедиться, что это реальное событие касания, а не ложное положительное.

$("body").on("touchstart", ".touchable", function() { //make touchable  items fire like a click event
var d1 = new Date();
var n1 = d1.getTime();
setTimeout(function() {
    $(".touchable").on("touchend", function(event) {
        var d2 = new Date();
        var n2 = d2.getTime();
        if (n2 - n1 <= 300) {
            $(event.target).trigger("click"); //dont do the action here just call real click handler
        }
    });
}, 50)}).on("click", "#myelement", function() {
//all the behavior i originally wanted
});