How to detect a long touch pressure with javascript for android and iphone?
Asked Answered
L

12

58

How to detect a long touch pressure with javascript for android and iphone? native javascript or jquery...

I want something that sound like :

<input type='button' onLongTouch='myFunc();' />
Lully answered 26/5, 2011 at 13:26 Comment(0)
P
83

The problem with using Touch End to detect the long touch is it won't work if you want the event to fire after a certain period of time. It is better to use a timer on touch start and clear the event timer on touch end. The following pattern can be used:

var onlongtouch; 
var timer;
var touchduration = 500; //length of time we want the user to touch before we do something

touchstart() {
    timer = setTimeout(onlongtouch, touchduration); 
}

touchend() {

    //stops short touches from firing the event
    if (timer)
        clearTimeout(timer); // clearTimeout, not cleartimeout..
}

onlongtouch = function() { //do something };
Pumpernickel answered 15/10, 2013 at 1:26 Comment(3)
This would be triggered on element drag drop as well. If you want to detect a real taphold that doesn't include touchmove, you should also clear the timer on touchmove event. (touchmove = touchend)Eads
@Eads While not sure if this is still the case... about to check googles second result.. this being the first.. The only thing i would add to your comment is also adding a tolerance range.. store the down position and check move positions are within distance.Harney
This needs timer = undefined; since timer is just an integer (docs: setTimeout()) - there does not seem to be a way to test whether a timer is still running. Edit: this other answer does that.Metrist
A
22

Here is an extended version of Joshua answer, as his code works well till user doesn't perform multitouch (you can tap screen with two fingers and function will be triggered two times, 4 fingers - 4 times). After some additional test scenarios I even triggered possibility to touch very freequently and receive function executing after each tap.

I added variable named 'lockTimer' which should lock any additional touchstarts before user trigger 'touchend'.

var onlongtouch; 
var timer;
var touchduration = 800; //length of time we want the user to touch before we do something

function touchstart(e) {
    e.preventDefault();
    if (!timer) {
        timer = setTimeout(onlongtouch, touchduration);
    }
}

function touchend() {
    //stops short touches from firing the event
    if (timer) {
        clearTimeout(timer);
        timer = null;
    }
}

onlongtouch = function() { 
    timer = null;
    document.getElementById('ping').innerText+='ping\n'; 
};

document.addEventListener("DOMContentLoaded", function(event) { 
    window.addEventListener("touchstart", touchstart, false);
    window.addEventListener("touchend", touchend, false);
});
<div id="ping"></div>
Actinomorphic answered 21/7, 2015 at 9:35 Comment(1)
You don't need an extra lockTimer variable since you can just check if timer is set or not.Annal
B
8

The solutions posted here ignore the fact that the user needs to touch the screen to initiate scroll. We only want the long-press behavior if the user is not trying to scroll.

function onLongPress(element, callback) {
  let timer;

  element.addEventListener('touchstart', () => { 
    timer = setTimeout(() => {
      timer = null;
      callback();
    }, 500);
  });

  function cancel() {
    clearTimeout(timer);
  }

  element.addEventListener('touchend', cancel);
  element.addEventListener('touchmove', cancel);
}

And then:

onLongPress(element, () => {
  console.log('Long pressed', element);
});
Beekeeping answered 13/2, 2020 at 12:29 Comment(2)
Best answer so far, thank you! Also might want to trap for context menu.Jed
This is the only tap-and-hold JS solution that actually worked out properly. Somehow, the other solutions have some issues when the div has multiple elements in it...Aardwolf
C
6

I've done it this way in my Android app:

  1. registered events listeners:

    var touchStartTimeStamp = 0;
    var touchEndTimeStamp   = 0;
    
    window.addEventListener('touchstart', onTouchStart,false);
    window.addEventListener('touchend', onTouchEnd,false);
    
  2. added functions:

    var timer;
    function onTouchStart(e) {
        touchStartTimeStamp = e.timeStamp;
    }
    
    function onTouchEnd(e) {
        touchEndTimeStamp = e.timeStamp;
    
        console.log(touchEndTimeStamp - touchStartTimeStamp);// in miliseconds
    }
    
  3. checked time difference and did my stuff

I hope this will help.

Cycloplegia answered 3/1, 2017 at 12:30 Comment(1)
this catches is a long-press on touch-end, but I don't think a long-press requires a touch-end.Handout
J
6

Building on the solution by @djanowski to handle touch scroll. This should also prevent context menu and selection on long press.

function onLongPress(element, callback) {
    var timeoutId;

    element.addEventListener('touchstart', function(e) {
        timeoutId = setTimeout(function() {
            timeoutId = null;
            e.stopPropagation();
            callback(e.target);
        }, 500);
    });

    element.addEventListener('contextmenu', function(e) {
        e.preventDefault();
    });

    element.addEventListener('touchend', function () {
        if (timeoutId) clearTimeout(timeoutId);
    });

    element.addEventListener('touchmove', function () {
        if (timeoutId) clearTimeout(timeoutId);
    });
}

onLongPress(document.getElementById('kitty1'), function(element) {
    alert('Meow from ' + element.outerHTML );
});

onLongPress(document.getElementById('kitty2'), function(element) {
    alert('Meow from ' + element.outerHTML );
});
img {
  max-width: 100%;
  -webkit-user-select: none; /* Safari */
  -ms-user-select: none; /* IE 10 and IE 11 */
  user-select: none; /* Standard syntax */
}
<p>Long press on kitty!  Kitty should meow on 500ms long press but not scroll</p>

<img id="kitty1" src="http://placekitten.com/300/400" />
<img id="kitty2" src="http://placekitten.com/300/300" />
Jed answered 14/6, 2021 at 20:59 Comment(0)
H
2

We can calculate the time difference when the touch started and when the touch end. If the calculated time difference exceed the touch duration then we use a function name taphold.

var touchduration = 300; 
var timerInterval;

function timer(interval) {

    interval--;

    if (interval >= 0) {
        timerInterval = setTimeout(function() {
                            timer(interval);
                        });
    } else {
        taphold();
    }

}

function touchstart() {
    timer(touchduration);
}

function touchend() {
    clearTimeout(timerInterval);
}

function taphold(){
    alert("taphold");
}


document.getElementById("xyz").addEventListener('touchstart',touchstart);
document.getElementById("xyz").addEventListener('touchend',touchend);
Halliehallman answered 4/9, 2014 at 13:18 Comment(1)
The idea I think for most people is for the function to be executed while it is being held, not after. Therefore touch end wouldn't work.Gibb
G
1

For cross platform developers:

Mouseup/down seemed to work okay on android - but not all devices ie (samsung tab4). Did not work at all on iOS.

Further research its seems that this is due to the element having selection and the native magnification interupts the listener.

This event listener enables a thumbnail image to be opened in a bootstrap modal, if the user holds the image for 500ms.

It uses a responsive image class therefore showing a larger version of the image. This piece of code has been fully tested upon (iPad/Tab4/TabA/Galaxy4):

var pressTimer;  
$(".thumbnail").on('touchend', function (e) {
   clearTimeout(pressTimer);
}).on('touchstart', function (e) {
   var target = $(e.currentTarget);
   var imagePath = target.find('img').attr('src');
   var title = target.find('.myCaption:visible').first().text();
   $('#dds-modal-title').text(title);
   $('#dds-modal-img').attr('src', imagePath);
   // Set timeout
   pressTimer = window.setTimeout(function () {
      $('#dds-modal').modal('show');
   }, 500)
});
Gerrit answered 23/10, 2015 at 13:21 Comment(0)
F
1

This better solution based on @Joshua, sometimes the code need to be called directly inside event (some web API require user acction to trigger something) for this case you can use this modification:

var longtouch;
var timer;
//length of time we want the user to touch before we do something
var touchduration = 500;

function touchstart() {
    longtouch = false;

    timer = setTimeout(function() {
       longtouch = true;
       timer = null
    }, touchduration);
}

function touchend() {
    if (timer) {
        clearTimeout(timer);
        timer = null;
    }
    if (longtouch) {
        // your long acction inside event
        longtouch = false;
    }
}

in setTimeout you set the flag to true and inside touchend, you check if it was set.

Froude answered 15/3, 2020 at 19:5 Comment(0)
S
0

This worked for my use-case i.e wanted to execute certain function for the time screen is touched.

let triggerInterval = 200; // in milliseconds
let timerId;

function touchstart(e) {
  // e.preventDefault();
  timerId = setInterval(yourFunction, triggerInterval);
}

function touchend(e) {
  clearInterval(timerId);
}

function yourFunction() {
  //   perform your logic
}

document.addEventListener("touchstart", touchstart);
document.addEventListener("touchend", touchend);

Note:- Smaller value in triggerInterval will execute yourFunction() more faster.

When you are done with your program, then you can remove the respective event Listeners:

document.removeEventListener("touchstart", touchstart);
document.removeEventListener("touchend", touchend);
Speciosity answered 24/5, 2021 at 14:12 Comment(1)
You need to change setInterval() to setTimeout() as setInterval() will end up calling yourFunction multiple times if the user holds for 400ms for example given that triggerInterval is set to 200.Saddlebag
P
0

I took a lot of the ideas presented in the answers here and combined them to form the following helper function that I use for long press functionality.

I've tested it pretty thoroughly in Chrome on Windows and Android and it handles both mouse and touch interactions consistently.

const addLongPressListener = ({ element, callback, delay = 500, preventClick = true }) => {
    var longPressTimeout;
    var longPressed = false;

    const longPressStart = (e) => {
        longPressTimeout = setTimeout(() => {
            longPressed = true;
            e.stopPropagation();
            callback && typeof callback === 'function' && callback(e);
        }, delay);
    };

    const longClickCancel = (e) => {
        longPressTimeout && clearTimeout(longPressTimeout);
    };

    const longTouchCancel = (e) => {
        longPressTimeout && clearTimeout(longPressTimeout);
        longPressed = false;
    };

    element.addEventListener('touchstart', (e) => longPressStart(e), false);
    element.addEventListener('mousedown', (e) => longPressStart(e), false);

    element.addEventListener('contextmenu', (e) => e.preventDefault(), false);
    element.addEventListener(
        'click',
        (e) => {
            if (preventClick && longPressed) e.preventDefault();
            longPressed = false;
        },
        false
    );

    element.addEventListener('touchend', (e) => longTouchCancel(e), false);
    element.addEventListener('touchmove', (e) => longTouchCancel(e), false);

    element.addEventListener('mouseup', (e) => longClickCancel(e), false);
    element.addEventListener('mousemove', (e) => longClickCancel(e), false);
};

In projects where I'm okay with modifying the Element prototype, I also add the following to make using the listener more like using the normal addEventListener.

Object.defineProperty(Element.prototype, 'addLongPressListener', {
    value: function (callback, params) {
        addLongPressListener({ element: this, callback, ...params });
    },
    enumerable: false,
});

With the Element prototype addition, using the listener looks something like this:

let target = document.getElementById('target');

target.addLongPressListener((e) => {
    console.log('Long Press');
});

And it looks something like this when using the optional parameters for controlling the delay timing and whether or not it blocks normal click events on a long press:

let target = document.getElementById('target');

target.addLongPressListener(
    (e) => {
        console.log('Extra Long Press');
    },
    { delay: 2000, preventClick: false }
);



Lastly, here's the full working code snippet to play around with:

const addLongPressListener = ({
  element,
  callback,
  delay = 500,
  preventClick = true
}) => {
  var longPressTimeout;
  var longPressed = false;

  const longPressStart = (e) => {
    longPressTimeout = setTimeout(() => {
      longPressed = true;
      e.stopPropagation();
      callback && typeof callback === 'function' && callback(e);
    }, delay);
  };

  const longClickCancel = (e) => {
    longPressTimeout && clearTimeout(longPressTimeout);
  };

  const longTouchCancel = (e) => {
    longPressTimeout && clearTimeout(longPressTimeout);
    longPressed = false;
  };

  element.addEventListener('touchstart', (e) => longPressStart(e), false);
  element.addEventListener('mousedown', (e) => longPressStart(e), false);

  element.addEventListener('contextmenu', (e) => e.preventDefault(), false);
  element.addEventListener(
    'click',
    (e) => {
      if (preventClick && longPressed) e.preventDefault();
      longPressed = false;
    },
    false
  );

  element.addEventListener('touchend', (e) => longTouchCancel(e), false);
  element.addEventListener('touchmove', (e) => longTouchCancel(e), false);

  element.addEventListener('mouseup', (e) => longClickCancel(e), false);
  element.addEventListener('mousemove', (e) => longClickCancel(e), false);
};

Object.defineProperty(Element.prototype, 'addLongPressListener', {
  value: function(callback, params) {
    addLongPressListener({
      element: this,
      callback,
      ...params
    });
  },
  enumerable: false,
});

let normalTarget = document.getElementById('normal_target');

normalTarget.addLongPressListener((e) => {
  console.log('Long Press');
});

let longTarget = document.getElementById('long_target');

longTarget.addLongPressListener(
  (e) => {
    console.log('Extra Long Press');
  }, {
    delay: 2000,
    preventClick: false
  }
);
body {
  margin: 0;
}

.container {
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: space-evenly;
  align-items: start;
  background-color: #252627;
}

.button {
  padding: 1rem;
  margin: 1rem;
  border: 2px solid #5f6368;
  border-radius: 1rem;
  background-color: #be32ff;
  color: #ffffff;
  font-size: 24px;
  font-weight: bold;
  Font-family: Roboto, Sans-serif;
  cursor: pointer;
}
<div class='container'>
  <span id='normal_target' class='button'>Normal Long Press</span>
  <span id='long_target' class='button'>Extra Long Press</span>
<div>
Postnatal answered 19/7 at 8:57 Comment(0)
B
0

This is a variant of @djanowsi's answer using promises

    function onlongtouch(elem, handler) {
        let promise = Promise.withResolvers(); // Initialize just in case a touchend or touchmove happens before the first touchstart
    
        elem.addEventListener('touchstart', (e) => {
            promise = Promise.withResolvers();
            setTimeout(() => promise.resolve(e), 300);
            promise.promise.then(handler).catch(() => {});  // Call the handler if the promise resolves, and ignore if the promise rejects.
        });
    
        elem.addEventListener('touchend', () => {promise.reject()});
        elem.addEventListener('touchmove', () => {promise.reject()});
    }

This works because if a promise resolves or rejects one time, all subsequent attempts to resolve or reject it are ignored.

Breckenridge answered 28/7 at 14:57 Comment(0)
R
-1

Long tap event that working in all browser

(function (a) {
        function n(b) { a.each("touchstart touchmove touchend touchcancel".split(/ /), function (d, e) { b.addEventListener(e, function () { a(b).trigger(e) }, false) }); return a(b) } function j(b) { function d() { a(e).data(h, true); b.type = f; jQuery.event.handle.apply(e, o) } if (!a(this).data(g)) { var e = this, o = arguments; a(this).data(h, false).data(g, setTimeout(d, a(this).data(i) || a.longclick.duration)) } } function k() { a(this).data(g, clearTimeout(a(this).data(g)) || null) } function l(b) {
            if (a(this).data(h)) return b.stopImmediatePropagation() ||
            false
        } var p = a.fn.click; a.fn.click = function (b, d) { if (!d) return p.apply(this, arguments); return a(this).data(i, b || null).bind(f, d) }; a.fn.longclick = function () { var b = [].splice.call(arguments, 0), d = b.pop(); b = b.pop(); var e = a(this).data(i, b || null); return d ? e.click(b, d) : e.trigger(f) }; a.longclick = { duration: 500 }; a.event.special.longclick = {
            setup: function () {
                /iphone|ipad|ipod/i.test(navigator.userAgent) ? n(this).bind(q, j).bind([r, s, t].join(" "), k).bind(m, l).css({ WebkitUserSelect: "none" }) : a(this).bind(u, j).bind([v,
                w, x, y].join(" "), k).bind(m, l)
            }, teardown: function () { a(this).unbind(c) }
        }; var f = "longclick", c = "." + f, u = "mousedown" + c, m = "click" + c, v = "mousemove" + c, w = "mouseup" + c, x = "mouseout" + c, y = "contextmenu" + c, q = "touchstart" + c, r = "touchend" + c, s = "touchmove" + c, t = "touchcancel" + c, i = "duration" + c, g = "timer" + c, h = "fired" + c
    })(jQuery);

Bind longclick event with time interval

 $('element').longclick(250, longClickHandler);

below function fire on Long Tap on touch device

function longClickHandler() {
    alter('Long tap Fired');
}
Rappee answered 24/3, 2017 at 4:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.