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

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

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

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

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

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

Средство состоит в том, чтобы вместо этого использовать touchstart:

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

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

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

ДОПОЛНЕНИЕ:

Возможно, более всеобъемлющий вопрос:

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

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

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

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

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

Ответ 1

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


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

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

  return false
});

Ответ 2

Это исправление, которое я "создаю", и вынимает 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

});

Ответ 3

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

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

Ответ 4

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

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

});

Ответ 5

Просто добавление 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; }.

Ответ 6

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

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

  //your code here
});

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

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

  //your code here
});

Ответ 7

Мне удалось выполнить следующий путь.

Easy Peasy...

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

Ответ 8

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

Если вы подключаетесь в событии 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
     }
}

Ответ 9

Ну... Все это супер сложно.

Если у вас есть модернизация, это без проблем.

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

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

Ответ 10

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

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

touchhend

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

Событие touchhend будет не запускать любые события мыши.


мышь

Событие 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>

Ответ 12

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

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()) {
      ...
   }
}

Ответ 13

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

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

$.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

Ответ 14

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

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

Ответ 15

Вы можете попробовать следующее:

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

Ответ 16

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

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

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

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

Ответ 17

В моем случае это сработало отлично:

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

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

Ответ 18

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

Наконец, вы можете binb.live( "touchstart", function (e) {e.stopPropagation();}); попросить событие touchstart прекратить распространение, живую комнату щелкнуть(), чтобы начать работу.

Это сработало для меня.

Ответ 19

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

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

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

Ответ 20

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

Ответ 21

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

$('#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 запускается в элементе, устанавливается флаг, а затем удаляется (и игнорируется), когда нажимается.

Ответ 22

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

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

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

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

Ответ 23

Если вы используете 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.

Может помочь кому-то в подобной ситуации!

Ответ 24

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

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 именно этого.

Ответ 25

Я пытаюсь это сделать, и пока это работает (но я только на Android/Phonegap, поэтому caveat 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...
  }

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

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

Ответ 26

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

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

Ответ 27

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

Плагин jQuery: Touch or Mouse

В итоге я написал плагин jQuery под названием " Touch or Mouse" (сокращенно 897 байт), который может определить, было ли событие вызвано сенсорный экран или мышь (без тестирования на сенсорную поддержку!). Это позволяет одновременно поддерживать сенсорный экран и мышь и полностью отделять их события.

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

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

Сначала нужно сделать т.е. события касания троса тела:

$(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.

Объяснение

  • Этот плагин необходимо вызвать, т.е. элемент body для отслеживания событий touchstart и touchend, таким образом, событие touchend не нужно запускать на триггер (т.е. ссылку или кнопку). Между этими двумя событиями касания этот плагин считает, что любое событие мыши вызывается прикосновением.
  • События мыши запускаются только после touchend, когда событие mouse запускается в ghostEventDelay (опция, 1000 мс по умолчанию) после touchend, этот плагин считает, что событие мыши вызывается нажатием.
  • При нажатии на элемент с использованием сенсорного экрана элемент получает состояние :active. Событие mouseleave запускается только после того, как элемент теряет это состояние, т.е. нажатие на другой элемент. Поскольку это может быть секунды (или минуты!) После того, как событие mouseenter было запущено, этот плагин отслеживает последний элемент mouseenter элемента: если последнее событие mouseenter было вызвано прикосновением, следующий mouseleave событие также считается вызванным прикосновением.

Ответ 29

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

// 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, который прикреплен к корневому документу и выполняется только тогда, когда тип события равен значению в "триггере". На сенсорных устройствах цепочка событий, скорее всего, будет "touchstart" , за которой следует 'click'; однако обработчик 'click' не будет выполнен, потому что "click" не соответствует начальному типу события, сохраненному в "триггере" ( "touchstart" ).

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

Вот код, с которым вы можете поиграть (попробуйте нажать на кнопку на сенсорном устройстве - не должно быть задержки нажатия): 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/

Ответ 30

UDPATE:

Я работал над реализацией, использующей события click и touchhend для одной и той же функции, и функция эффективно блокирует события, если тип изменяется. Моя цель состояла в том, чтобы иметь более гибкий интерфейс приложения - я хотел сократить время начала события в контуре обратной связи UI.

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

Вот легкая реализация на основе API, которую я упростил для демонстрационных целей. Он демонстрирует, как использовать функциональные возможности для элемента collapse.

var tv = {
    /**
     * @method eventValidator()
     * @desc responsible for validating event of the same type.
     * @param {Object} e - event object
     * @param {Object} element - element event cache
     * @param {Function} callback - callback to invoke for events of the same type origin
     * @param {Object} [context] - context to pass to callback function
     * @param {Array} [args] - arguments array to pass in with context. Requires context to be passed
     * @return {Object} - new event cache
     */
    eventValidator: function(e, element, callback, context, args){
        if(element && element.type && element.type !== e.type){
            e.stopPropagation();
            e.preventDefault();
            return tv.createEventCacheObj({}, true);
        } else {
            element = tv.createEventCacheObj(e);
            (typeof context === "object" ? callback.apply(context, args) : callback());
            return element;
        }
    },

    /**
     * @method createEventCacheObj()
     * @param {Object} event - event object
     * @param {String} [event.type] - event type
     * @param {Number} [event.timeStamp] - time of event in MS since load
     * @param {Boolean} [reset=false] - flag to reset the object
     * @returns {{type: *, time: string}}
     */
    createEventCacheObj: function (event, reset){
        if(typeof reset !== 'boolean') reset = false;
        return {
            type: !reset ? event.type : null,
            time: !reset ? (event.timeStamp).toFixed(2): null
        };
    }
};

// Here is where the magic happens
var eventCache = [];
var pos = 0;

var $collapses = document.getElementsByClassName('tv-collapse__heading');
    Array.prototype.forEach.call($collapses, function(ele){
        ele.addEventListener('click', toggleCollapse);
        ele.addEventListener('touchend', toggleCollapse);

        // Cache mechanism
        ele.setAttribute('data-event-cache', String(pos++));
    });

/**
 * @func toggleCollapse()
 * @param {Object} e - event object
 * @desc responsible for toggling the state of a collapse element
 */
function toggleCollapse(e){
    eventCache[pos] = tv.eventValidator(e, eventCache[pos], function(){
       // Any event which isn't blocked will run the callback and its content
       // the context and arguments of the anonymous function match the event function context and arguments (assuming they are passed using the last two parameters of tv.eventValidator)

    }, this, arguments);
}

Исходный ответ:

Вот ответ, который является модификацией ответа Рафаэля Фрагосо - чистый JS.

(function(){
  
  button = document.getElementById('sayHi');
  
  button.addEventListener('touchstart', ohHai);
  button.addEventListener('click', ohHai);

  function ohHai(event){
    event.stopPropagation();
    event.preventDefault();
    console.log('ohHai is:', event.type);
  };
})();
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SO - Answer</title>
</head>
<body>
  <button id="sayHi">Anyone there?</button>
</body>
</html>