предотвратить тачстарт при смахивании

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

Уловка состоит в том, чтобы объединить два. Я не хочу, чтобы строка выбиралась, если вы на самом деле прокручиваете список. Вот что я нашел:

Не срабатывает при прокрутке:

  • щелкнуть
  • мышь

Срабатывает при прокрутке:

  • мышка
  • сенсорный старт
  • прикосновение

Простое решение - просто придерживаться события щелчка. Но мы обнаружили, что на некоторых устройствах Blackberry существует ОЧЕНЬ заметная задержка между сенсорным запуском и последующим запуском щелчка или мыши. Эта задержка достаточно значительна, чтобы ее нельзя было использовать на этих устройствах.

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

Как лучше всего решить эту проблему?


person DA.    schedule 15.08.2011    source источник
comment
У меня такая же проблема, я добавил награду за ваш вопрос.   -  person MacMac    schedule 19.02.2012
comment
были некоторые из тех же проблем, в конечном итоге построил родной.   -  person Davin Tryon    schedule 19.02.2012
comment
Напишу ответ. В конечном итоге мы зарегистрировали позицию при touchstart, а затем зарегистрировали позицию при touchstart. Если они существенно различались, мы ничего не делали, так как предполагали, что намеревались прокрутить. Если они близки, мы устанавливаем этот флажок, предполагая, что это должно было быть касанием. Чтобы по-прежнему разрешать события щелчка (необходимые для клавиатур), мы бы добавили класс при сенсорном запуске, который временно блокировал бы любые события щелчка.   -  person DA.    schedule 19.02.2012
comment
Вы можете привести пример, как это сделать с помощью jQuery?   -  person MacMac    schedule 19.02.2012
comment
Ты здесь, приятель? Напишите ответ, когда сможете.   -  person MacMac    schedule 20.02.2012
comment
@lolwut Я здесь! Мне нужно покопаться в старом коде сегодня вечером, но, надеюсь, к концу дня мы что-нибудь вам дадим.   -  person DA.    schedule 20.02.2012
comment
@lolwut посмотри мой ответ. Сравните его и с ответом Netlight_Digital_Media, это может быть лучшим вариантом, чем мой.   -  person DA.    schedule 21.02.2012


Ответы (12)


В основном вы хотите определить, что такое смахивание, а что - щелчок.

Мы можем установить некоторые условия:

  1. Смахивание - это прикосновение к точке p1, затем перемещение пальца к точке p2, при этом палец по-прежнему остается на экране, а затем отпускание.
  2. Щелчок - это когда вы касаетесь начала касания и заканчиваете касание одного и того же элемента.

Итак, если вы сохраните координаты того места, где произошел ваш touchStart, вы можете измерить разницу в touchEnd. Если изменение достаточно велико, считайте это смахиванием, в противном случае считайте это щелчком.

Кроме того, если вы хотите сделать это действительно аккуратно, вы также можете определить, какой элемент вы «наводите» пальцем во время touchMove, и если вы еще не находитесь на элементе, на котором вы начали щелкать, вы можете запустить clickCancel метод, удаляющий блики и т. д.

// grab an element which you can click just as an example
var clickable = document.getElementById("clickableItem"),
// set up some variables that we need for later
currentElement,
clickedElement;

// set up touchStart event handler
var onTouchStart = function(e) {
    // store which element we're currently clicking on
    clickedElement = this;
    // listen to when the user moves finger
    this.addEventListener("touchMove" onTouchMove);
    // add listener to when touch end occurs
    this.addEventListener("touchEnd", onTouchEnd);
};
// when the user swipes, update element positions to swipe
var onTouchMove = function(e) {
    // ... do your scrolling here

    // store current element
    currentElement = document.elementFromPoint(x, y);
    // if the current element is no longer the same as we clicked from the beginning, remove highlight
    if(clickedElement !== currentElement) {
        removeHighlight(clickedElement);
    }
};
// this is what is executed when the user stops the movement
var onTouchEnd = function(e) {
    if(clickedElement === currentElement) {
        removeHighlight(clickedElement);
        // .... execute click action
    }

    // clean up event listeners
    this.removeEventListener("touchMove" onTouchMove);
    this.removeEventListener("touchEnd", onTouchEnd);
};
function addHighlight(element) {
    element.className = "highlighted";
}
function removeHighlight(element) {
    element.className = "";
}
clickable.addEventListener("touchStart", onTouchStart);

Затем вам нужно будет добавить слушателей к прокручиваемому элементу, но вам не придется беспокоиться о том, что произойдет, если палец переместится между touchStart и touchEnd.

var scrollable = document.getElementById("scrollableItem");

// set up touchStart event handler
var onTouchStartScrollable = function(e) {
    // listen to when the user moves finger
    this.addEventListener("touchMove" onTouchMoveScrollable);
    // add listener to when touch end occurs
    this.addEventListener("touchEnd", onTouchEndScrollable);
};
// when the user swipes, update element positions to swipe
var onTouchMoveScrollable = function(e) {
    // ... do your scrolling here
};
// this is what is executed when the user stops the movement
var onTouchEndScrollable = function(e) {
    // clean up event listeners
    this.removeEventListener("touchMove" onTouchMoveScrollable);
    this.removeEventListener("touchEnd", onTouchEndScrollable);
};
scrollable.addEventListener("touchStart", onTouchStartScrollable);

// Саймон А.

person Netlight_Digital_Media    schedule 20.02.2012

Вот то, что я в конечном итоге придумал, чтобы можно было прокручивать список элементов с помощью смахивания, но также каждый элемент можно было «запускать» с помощью касания. Кроме того, вы все еще можете использовать с клавиатурой (с помощью onclick).

Я думаю, что это похоже на ответ Netlight_Digital_Media. Мне нужно изучить это еще немного.

$(document)
// log the position of the touchstart interaction
.bind('touchstart', function(e){ 
  touchStartPos = $(window).scrollTop();
})
// log the position of the touchend interaction
.bind('touchend', function(e){
  // calculate how far the page has moved between
  // touchstart and end. 
  var distance = touchStartPos - $(window).scrollTop();

  var $clickableItem; // the item I want to be clickable if it's NOT a swipe

  // adding this class for devices that
  // will trigger a click event after
  // the touchend event finishes. This 
  // tells the click event that we've 
  // already done things so don't repeat

  $clickableItem.addClass("touched");      

  if (distance > 20 || distance < -20){
        // the distance was more than 20px
        // so we're assuming they intended
        // to swipe to scroll the list and
        // not selecting a row. 
    } else {
        // we'll assume it was a tap 
        whateverFunctionYouWantToTriggerOnTapOrClick()
    }
});


$($clickableItem).live('click',function(e){
 // for any non-touch device, we need 
 // to still apply a click event
 // but we'll first check to see
 // if there was a previous touch
 // event by checking for the class
 // that was left by the touch event.
if ($(this).hasClass("touched")){
  // this item's event was already triggered via touch
  // so we won't call the function and reset this for
  // the next touch by removing the class
  $(this).removeClass("touched");
} else {
  // there wasn't a touch event. We're
  // instead using a mouse or keyboard
  whateverFunctionYouWantToTriggerOnTapOrClick()
}
});
person DA.    schedule 21.02.2012
comment
Хм, мой iPhone зарегистрировал ошибку TypeError: 'undefined' is not an object, когда я нажимаю на строку. Я заменил whateverFunctionYouWantToTriggerOnTapOrClick() на alert('tapped'), но он не получил ответа. - person MacMac; 21.02.2012
comment
да, это не рабочий код ... вам нужно будет добавить свою логику по мере необходимости. Просто пример, демонстрирующий используемую логику. - person DA.; 21.02.2012
comment
lolwut: вам нужно будет получить свои строки и назначить их переменной $clickableItem, иначе clickableItem будет undefined, когда вы вызовете addClass. Вот так: var clickableItem = $("#row")[0]; - person Netlight_Digital_Media; 23.02.2012
comment
Еще одна проблема, которую я вижу здесь, - это использование .live (), который устарел с jQuery v1.7 и удален в 1.9. Вместо этого используйте .on (). - person Silkster; 17.04.2013
comment
Я тестировал этот подход в своем проекте. Единственная проблема заключается в том, что если пользователь прокручивает вверх, а затем вниз (почти до первой позиции), это считается касанием. - person Ehsan; 01.08.2014

Цитата из Д.А.:

Это рабочий пример:

var touch_pos;
$(document).on('touchstart', '.action-feature', function(e) {
  e.preventDefault();
  touch_pos = $(window).scrollTop();
}).on('click touchend', '.action-feature', function(e) {
  e.preventDefault();
  if(e.type=='touchend' && (Math.abs(touch_pos-$(window).scrollTop())>3)) return;
  alert("only accessed when it's a click or not a swipe");
});
person josualeonard    schedule 29.05.2013

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

Tocca.js: https://github.com/GianlucaGuarini/Tocca.js

Он довольно гибкий и распознает касание, а также смахивание, двойное касание и т. Д.

person cdcdcd    schedule 27.05.2014
comment
Согласен. У меня было несколько элементов, которые нужно было щелкнуть дважды, а не один раз только на мобильных устройствах, поэтому я заменил все события «щелчок» событиями «касания», и теперь настольные и мобильные устройства работают одинаково. :) - person yitwail; 04.04.2015

У меня была такая же проблема, вот быстрое решение, которое мне подходит

$(document).on('touchstart', 'button', function(evt){ 
    var oldScrollTop = $(window).scrollTop();
    window.setTimeout( function() {
        var newScrollTop = $(window).scrollTop();
        if (Math.abs(oldScrollTop-newScrollTop)<3) $button.addClass('touchactive');
    }, 200);
});

в основном вместо немедленной обработки сенсорного запуска подождите несколько миллисекунд (200 мс в этом примере), затем проверьте позицию прокрутки, изменилась ли позиция прокрутки, тогда нам не нужно обрабатывать сенсорный запуск.

person Mahes    schedule 27.03.2014

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

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

    $('.listObject').live('touchstart', touchScroll);
    
  2. Затем каждому элементу присвойте атрибут объекта данных, определяющий вызываемую функцию.

    <button class='listObject' data-object=alert('You are alerted !')>Alert Me</button>
    

Следующая функция будет эффективно различать касание и прокрутку или пролистывание.

function touchScroll(e){

    var objTarget = $(event.target);

    if(objTarget.attr('data-object')){
        var fn = objTarget.attr('data-object'); //function to call if tapped    
    }   

    if(!touchEnabled){// default if not touch device
        eval(fn);
        console.log("clicked", 1);
        return;
    }

    $(e.target).on('touchend', function(e){
        eval(fn); //trigger the function
        console.log("touchEnd")      
        $(e.target).off('touchend');
    });

    $(e.target).on('touchmove', function(e){
        $(e.target).off('touchend');
        console.log("moved")
    }); 

}
person alQemist    schedule 29.05.2013
comment
Хммм, жаль, что это не устраняет призрачный щелчок по стандартному браузеру Android, иначе это решение было бы золотым. Нужно больше экспериментировать ... - person smets.kevin; 24.09.2013

Я придумал это, так как мне нужно было глобальное событие, которое также может предотвратить динамическое связывание. Таким образом, preventDefault() должен быть вызван в прослушивателе событий, для этого делается ссылка на событие касания.

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

$('li a').on('ontouchstart' in document.documentElement === true ? 'touchClick' : 'click', handleClick);

if('ontouchstart' in document.documentElement === true) {
    var clickedEl = null;
    var touchClickEvent = $.Event('touchClick');
    var validClick = false;
    var pos = null;
    var moveTolerance = 15;
    $(window).on('touchstart', function(e) {
        /* only handle touch with one finger */
        if(e.changedTouches.length === 1) {
            clickedEl = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY);
            pos = {x: e.touches[0].clientX, y: e.touches[0].clientY};
            validClick = true;
        } else {
            validClick = false;
        }
    }).on("touchmove", function(e) {
        var currentEl = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY);
        if(
            e.changedTouches.length > 1 ||
            (!$(clickedEl).is(currentEl) && !$.contains(clickedEl, currentEl)) ||
            (Math.abs(pos.y - e.touches[0].clientY) > moveTolerance && Math.abs(pos.x - e.touches[0].clientX) > moveTolerance)
        ) {
            validClick = false;
        }
    }).on("touchend", function(e) {
        if(validClick) {
            /* this allowes calling of preventDefault on the touch chain */
            touchClickEvent.originalEvent = e;
            /* target is used in jQuery event delegation */
            touchClickEvent.target = e.target;
            $(clickedEl).trigger(touchClickEvent);
        }
    });
}
person DavidH    schedule 15.01.2021

В jQuery Mobile есть событие .tap(), которое, похоже, имеет ожидаемое поведение:

Событие касания jQuery Mobile запускается после быстрого полного события касания, которое происходит на одном целевом объекте. Это жест, эквивалентный стандартному событию щелчка, которое запускается при отпускании сенсорного жеста.

Это не обязательно ответ на вопрос, но может быть полезной альтернативой для некоторых.

person Wex    schedule 28.01.2015

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

Я использовал toggleSlide () jQuery, чтобы открывать и закрывать блоки ввода, запуская слайд при сенсорном запуске. Проблема заключалась в том, что, когда пользователь хотел прокрутить, открывался затронутый div. Чтобы этого не произошло (или отменить его до того, как пользователь заметил), я добавил в документ событие touchslide, которое закроет последний затронутый div.

Более подробно, вот фрагмент кода:

var lastTouched;

document.addEventListener('touchmove',function(){
    lastTouched.hide();
});

$('#button').addEventListener('touchstart',function(){
    $('#slide').slideToggle();
    lastTouched = $('#slide');
});

Глобальная переменная хранит последний затронутый div, и если пользователь проведет пальцем по экрану, событие document.touchmove скроет этот div. Иногда вы видите мерцание выпадающего div, но он работает для того, что мне нужно, и мне достаточно просто придумать его.

person Abraham Brookes    schedule 02.07.2012

Я использую этот фрагмент кода, чтобы кнопки срабатывали (на touchchend) только в том случае, если они не смахиваются:

var startY;
var yDistance;

function touchHandler(event) {
    touch = event.changedTouches[0];
    event.preventDefault();
}

$('.button').on("touchstart", touchHandler, true);
$('.button').on("touchmove", touchHandler, true);

$('.button').on("touchstart", function(){
    startY = touch.clientY;
});

$('.button').on('touchend', function(){

    yDistance = startY - touch.clientY;

    if(Math.abs(yDist) < 30){

        //button response here, only if user is not swiping
        console.log("button pressed")
    }
});
person LocDog    schedule 21.11.2014

если вы хотите сделать это для нескольких элементов, а также вам нужны события мыши и указателя:

var elems = $('YOURMULTISELECTOR'); // selector for multiple elements
elems.unbind('mousdown pointerdown touchstart touchmove mouseup pointerup touchend');
var elem = null;
elems.on('mousdown pointerdown touchstart', function (e) {
    elem = yourSingleSelector(e);
}).on('touchmove', function (e) {
    elem = null;                
}).on('mouseup pointerup touchend', function (e) { 
    if (elem == yourSingleSelector(e)) {                    
        // do something
    }
});
person A.J.Bauer    schedule 12.12.2017

person    schedule
comment
Это как-то отменяет для меня действие прокрутки. - person LukeS; 29.04.2016
comment
Отлично! Порядок событий - или, лучше сказать, присвоение переменной touchmoved очень важно. Это не сработает, если вы поменяете их местами (по крайней мере, с ванильным JS это не так) - person ggzone; 13.03.2018
comment
Спасибо. Я 5 часов пытался найти это решение! - person Marco Romano; 02.08.2020