Disable selection context menu in iOS safari - javascript

I want to disable the default context menu that appears after a certain text is selected in iOS Safari (web browser). Is that possible?

The only way i found was by removing the selection and select again with javascript.
Have a look at my code:
/* prevent ios edit-menu */
if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) {
!function(){
var target = document.body; // the element where the edit menue should be disabled
var preventSelRecursion;
document.addEventListener('selectionchange', function(e){
var S = getSelection();
if (!S.rangeCount) return;
if (S.isCollapsed) return;
var r = S.getRangeAt(0);
if (!target.contains(r.commonAncestorContainer)) return;
if (preventSelRecursion) return;
iosSelMenuPrevent();
}, false);
var iosSelMenuPrevent = debounce(function(){
var S = getSelection();
var r = S.getRangeAt(0);
preventSelRecursion = true;
S = getSelection();
S.removeAllRanges();
setTimeout(function(){ // make remove-add-selection removes the menu
S.addRange(r);
setTimeout(function(){
preventSelRecursion = false;
});
},4);
},800); // if no selectionchange during 800ms : remove the menu
/* helper-function */
function debounce(fn, delay) {
var timer = null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
}();
}

It is possible, see this example. Basically, the important part is to set the right css atributes:
body { -webkit-touch-callout: none !important; }
a { -webkit-user-select: none !important; }
Also, here is a question which solves a similar issue

According to onclick blocks copy+paste on Mobile Safari?, if the text is in an element that has an onclick event, the context menu won't be displayed.

Inspired by Hans Gustavson's answer, I propose a simpler solution in TypeScript:
function disableIosSafariCallout(this: Window, event: any) {
const s = this.getSelection();
if ((s?.rangeCount || 0) > 0) {
const r = s?.getRangeAt(0);
s?.removeAllRanges();
setTimeout(() => {
s?.addRange(r!);
}, 50);
}
}
document.ontouchend = disableIosSafariCallout.bind(window);
This solution is actually a workaround. When you select a text, you might still see the text selection callout shows and then disappear immediately. I am not sure whether Hans Gustavson's answer has the same defect...

Related

How to get addEventListener click event to work on IOS with vanilla javascript

So I encountered something weird today (at least to me). I'm trying to do a basic click event that adds and removes a class to a div with some css animations. This is an image slider I'm building. It works fine on Safari, Chrome etc on desktop. But on Iphone it seems like it only works the first time it's clicked or touched. Looks like it doesn't remove the class so it can be added again..
I did try to add this to check for user agent and adding touchstart with no luck:
var ua = navigator.userAgent,
event = ua.match(/iPad/i) || ua.match(/iPhone/) ? "touchstart" : "click";
I also added all the prefixes I could find to the css but since it is working the first time it's probably not the issue.
Hope someone have an idea of what's going on.
const clientBtn = document.querySelectorAll(".client-btn");
let clientSliderContainer = document.querySelectorAll(
".field-clientslider-container__wrapper"
);
clientSliderContainer[0].style.display = "grid";
let clientNumber = 0;
clientBtn.forEach(function (button) {
button.addEventListener('click', function () {
clientSliderContainer[clientNumber].style.display = "none";
clientSliderContainer[clientNumber].classList.remove("fadein");
if (button.classList.contains("client-slider-prev-btn")) {
clientNumber--;
if (clientNumber < 0) {
clientNumber = clientSliderContainer.length - 1;
}
clientSliderContainer[clientNumber].style.display = "grid";
clientSliderContainer[clientNumber].classList.add("fadein");
}
if (button.classList.contains("client-slider-next-btn")) {
clientNumber++;
if (clientNumber > clientSliderContainer.length - 1) {
clientNumber = 0;
}
clientSliderContainer[clientNumber].style.display = "grid";
clientSliderContainer[clientNumber].classList.add("fadein");
}
});
});
Ok so I found out it indeed was that, my class was not being removed properly. I thought I handled that by removing the class in the beginning of my listener callback. But I just made a fix where I did a setTimeout() to remove the class after a few milliseconds instead. That did the trick.
clientBtn.forEach(function (button) {
button.addEventListener('click', function (event) {
clientSliderContainer[clientNumber].style.display = "none";
if (event.currentTarget.classList.contains("client-slider-prev-btn")) {
clientNumber--;
if (clientNumber < 0) {
clientNumber = clientSliderContainer.length - 1;
}
clientSliderContainer[clientNumber].style.display = "grid";
clientSliderContainer[clientNumber].classList.add("fadein");
setTimeout(function() {
clientSliderContainer[clientNumber].classList.remove("fadein");
}, 1000);
}
if (event.currentTarget.classList.contains("client-slider-next-btn")) {
clientNumber++;
if (clientNumber > clientSliderContainer.length - 1) {
clientNumber = 0;
}
clientSliderContainer[clientNumber].style.display = "grid";
clientSliderContainer[clientNumber].classList.add("fadein");
setTimeout(function() {
clientSliderContainer[clientNumber].classList.remove("fadein");
}, 1000);
}
});
});

Detect URL if it is already opened and throw pop-up : HTML+JS [duplicate]

I want to check with JavaScript if the user has already opened my website in another tab in their browser.
It seems I cannot do that with pagevisibility...
The only way I see is to use WebSocket based on a session cookie, and check if the client has more than one socket. But by this way, from current tab, I have to ask my server if this user has a tab opened right next to their current browser tab. It is a little far-fetched!
Maybe with localstorage?
The shorter version with localStorage and Storage listener
<script type="text/javascript">
// Broadcast that you're opening a page.
localStorage.openpages = Date.now();
var onLocalStorageEvent = function(e){
if(e.key == "openpages"){
// Listen if anybody else is opening the same page!
localStorage.page_available = Date.now();
}
if(e.key == "page_available"){
alert("One more page already open");
}
};
window.addEventListener('storage', onLocalStorageEvent, false);
</script>
Update:
Works on page crash as well.
Stimulate page crash in chrome: chrome://inducebrowsercrashforrealz
Live demo
Using local storage I created a simple demo that should accomplish what your looking to do. Basically, it simply maintains a count of currently opened windows. When the window is closed the unload events fire and remove it from the total window count.
When you first look at it, you may think there's more going on than there really is. Most of it was a shotty attempt to add logic into who was the "main" window, and who should take over as the "main" window as you closed children. (Hence the setTimeout calls to recheck if it should be promoted to a main window) After some head scratching, I decided it would take too much time to implement and was outside the scope of this question. However, if you have two windows open (Main, and Child) and you close the Main, the child will be promoted to a main.
For the most part you should be able to get the general idea of whats going on and use it for your own implementation.
See it all in action here:
http://jsbin.com/mipanuro/1/edit
Oh yeah, to actually see it in action... Open the link in multiple windows. :)
Update:
I've made the necessary changes to have the the local storage maintain the "main" window. As you close tabs child windows can then become promoted to a main window. There are two ways to control the "main" window state through a parameter passed to the constructor of WindowStateManager. This implementation is much nicer than my previous attempt.
JavaScript:
// noprotect
var statusWindow = document.getElementById('status');
(function (win)
{
//Private variables
var _LOCALSTORAGE_KEY = 'WINDOW_VALIDATION';
var RECHECK_WINDOW_DELAY_MS = 100;
var _initialized = false;
var _isMainWindow = false;
var _unloaded = false;
var _windowArray;
var _windowId;
var _isNewWindowPromotedToMain = false;
var _onWindowUpdated;
function WindowStateManager(isNewWindowPromotedToMain, onWindowUpdated)
{
//this.resetWindows();
_onWindowUpdated = onWindowUpdated;
_isNewWindowPromotedToMain = isNewWindowPromotedToMain;
_windowId = Date.now().toString();
bindUnload();
determineWindowState.call(this);
_initialized = true;
_onWindowUpdated.call(this);
}
//Determine the state of the window
//If its a main or child window
function determineWindowState()
{
var self = this;
var _previousState = _isMainWindow;
_windowArray = localStorage.getItem(_LOCALSTORAGE_KEY);
if (_windowArray === null || _windowArray === "NaN")
{
_windowArray = [];
}
else
{
_windowArray = JSON.parse(_windowArray);
}
if (_initialized)
{
//Determine if this window should be promoted
if (_windowArray.length <= 1 ||
(_isNewWindowPromotedToMain ? _windowArray[_windowArray.length - 1] : _windowArray[0]) === _windowId)
{
_isMainWindow = true;
}
else
{
_isMainWindow = false;
}
}
else
{
if (_windowArray.length === 0)
{
_isMainWindow = true;
_windowArray[0] = _windowId;
localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(_windowArray));
}
else
{
_isMainWindow = false;
_windowArray.push(_windowId);
localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(_windowArray));
}
}
//If the window state has been updated invoke callback
if (_previousState !== _isMainWindow)
{
_onWindowUpdated.call(this);
}
//Perform a recheck of the window on a delay
setTimeout(function()
{
determineWindowState.call(self);
}, RECHECK_WINDOW_DELAY_MS);
}
//Remove the window from the global count
function removeWindow()
{
var __windowArray = JSON.parse(localStorage.getItem(_LOCALSTORAGE_KEY));
for (var i = 0, length = __windowArray.length; i < length; i++)
{
if (__windowArray[i] === _windowId)
{
__windowArray.splice(i, 1);
break;
}
}
//Update the local storage with the new array
localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(__windowArray));
}
//Bind unloading events
function bindUnload()
{
win.addEventListener('beforeunload', function ()
{
if (!_unloaded)
{
removeWindow();
}
});
win.addEventListener('unload', function ()
{
if (!_unloaded)
{
removeWindow();
}
});
}
WindowStateManager.prototype.isMainWindow = function ()
{
return _isMainWindow;
};
WindowStateManager.prototype.resetWindows = function ()
{
localStorage.removeItem(_LOCALSTORAGE_KEY);
};
win.WindowStateManager = WindowStateManager;
})(window);
var WindowStateManager = new WindowStateManager(false, windowUpdated);
function windowUpdated()
{
//"this" is a reference to the WindowStateManager
statusWindow.className = (this.isMainWindow() ? 'main' : 'child');
}
//Resets the count in case something goes wrong in code
//WindowStateManager.resetWindows()
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id='status'>
<span class='mainWindow'>Main Window</span>
<span class='childWindow'>Child Window</span>
</div>
</body>
</html>
CSS:
#status
{
display:table;
width:100%;
height:500px;
border:1px solid black;
}
span
{
vertical-align:middle;
text-align:center;
margin:0 auto;
font-size:50px;
font-family:arial;
color:#ba3fa3;
display:none;
}
#status.main .mainWindow,
#status.child .childWindow
{
display:table-cell;
}
.mainWindow
{
background-color:#22d86e;
}
.childWindow
{
background-color:#70aeff;
}
(2021) You can use BroadcastChannel to communicate between tabs of the same origin.
For example, put the following at the top level of your js code, then test by opening 2 tabs:
const bc = new BroadcastChannel("my-awesome-site");
bc.onmessage = (event) => {
if (event.data === `Am I the first?`) {
bc.postMessage(`No you're not.`);
alert(`Another tab of this site just got opened`);
}
if (event.data === `No you're not.`) {
alert(`An instance of this site is already running`);
}
};
bc.postMessage(`Am I the first?`);
I know it is late, but maybe help someone
This snippet of code, will detect how many tabs are open and how many are active (visible) and if none of tabs is active, it will choose last opened tab, as active one.
This code will handle windows/tab crash too and it will refresh the count at crash.
Because localStorage is not supported on Stack Overflow currently, please test here.
<html>
<body>
Open in several tabs or windows
<div id="holder_element"></div>
<script type="text/javascript">
//localStorage.clear();
manage_crash();
//Create a windows ID for each windows that is oppened
var current_window_id = Date.now() + "";//convert to string
var time_period = 3000;//ms
//Check to see if PageVisibility API is supported or not
var PV_API = page_visibility_API_check();
/************************
** PAGE VISIBILITY API **
*************************/
function page_visibility_API_check ()
{
var page_visibility_API = false;
var visibility_change_handler = false;
if ('hidden' in document)
{
page_visibility_API = 'hidden';
visibility_change_handler = 'visibilitychange';
}
else
{
var prefixes = ['webkit','moz','ms','o'];
//loop over all the known prefixes
for (var i = 0; i < prefixes.length; i++){
if ((prefixes[i] + 'Hidden') in document)
{
page_visibility_API = prefixes[i] + 'Hidden';
visibility_change_handler = prefixes[i] + 'visibilitychange';
}
}
}
if (!page_visibility_API)
{
//PageVisibility API is not supported in this device
return page_visibility_API;
}
return {"hidden": page_visibility_API, "handler": visibility_change_handler};
}
if (PV_API)
{
document.addEventListener(PV_API.handler, function(){
//console.log("current_window_id", current_window_id, "document[PV_API.hidden]", document[PV_API.hidden]);
if (document[PV_API.hidden])
{
//windows is hidden now
remove_from_active_windows(current_window_id);
//skip_once = true;
}
else
{
//windows is visible now
//add_to_active_windows(current_window_id);
//skip_once = false;
check_current_window_status ();
}
}, false);
}
/********************************************
** ADD CURRENT WINDOW TO main_windows LIST **
*********************************************/
add_to_main_windows_list(current_window_id);
//update active_window to current window
localStorage.active_window = current_window_id;
/**************************************************************************
** REMOVE CURRENT WINDOWS FROM THE main_windows LIST ON CLOSE OR REFRESH **
***************************************************************************/
window.addEventListener('beforeunload', function ()
{
remove_from_main_windows_list(current_window_id);
});
/*****************************
** ADD TO main_windows LIST **
******************************/
function add_to_main_windows_list(window_id)
{
var temp_main_windows_list = get_main_windows_list();
var index = temp_main_windows_list.indexOf(window_id);
if (index < 0)
{
//this windows is not in the list currently
temp_main_windows_list.push(window_id);
}
localStorage.main_windows = temp_main_windows_list.join(",");
return temp_main_windows_list;
}
/**************************
** GET main_windows LIST **
***************************/
function get_main_windows_list()
{
var temp_main_windows_list = [];
if (localStorage.main_windows)
{
temp_main_windows_list = (localStorage.main_windows).split(",");
}
return temp_main_windows_list;
}
/**********************************************
** REMOVE WINDOWS FROM THE main_windows LIST **
***********************************************/
function remove_from_main_windows_list(window_id)
{
var temp_main_windows_list = [];
if (localStorage.main_windows)
{
temp_main_windows_list = (localStorage.main_windows).split(",");
}
var index = temp_main_windows_list.indexOf(window_id);
if (index > -1) {
temp_main_windows_list.splice(index, 1);
}
localStorage.main_windows = temp_main_windows_list.join(",");
//remove from active windows too
remove_from_active_windows(window_id);
return temp_main_windows_list;
}
/**************************
** GET active_windows LIST **
***************************/
function get_active_windows_list()
{
var temp_active_windows_list = [];
if (localStorage.actived_windows)
{
temp_active_windows_list = (localStorage.actived_windows).split(",");
}
return temp_active_windows_list;
}
/*************************************
** REMOVE FROM actived_windows LIST **
**************************************/
function remove_from_active_windows(window_id)
{
var temp_active_windows_list = get_active_windows_list();
var index = temp_active_windows_list.indexOf(window_id);
if (index > -1) {
temp_active_windows_list.splice(index, 1);
}
localStorage.actived_windows = temp_active_windows_list.join(",");
return temp_active_windows_list;
}
/********************************
** ADD TO actived_windows LIST **
*********************************/
function add_to_active_windows(window_id)
{
var temp_active_windows_list = get_active_windows_list();
var index = temp_active_windows_list.indexOf(window_id);
if (index < 0)
{
//this windows is not in active list currently
temp_active_windows_list.push(window_id);
}
localStorage.actived_windows = temp_active_windows_list.join(",");
return temp_active_windows_list;
}
/*****************
** MANAGE CRASH **
******************/
//If the last update didn't happened recently (more than time_period*2)
//we will clear saved localStorage's data and reload the page
function manage_crash()
{
if (localStorage.last_update)
{
if (parseInt(localStorage.last_update) + (time_period * 2) < Date.now())
{
//seems a crash came! who knows!?
//localStorage.clear();
localStorage.removeItem('main_windows');
localStorage.removeItem('actived_windows');
localStorage.removeItem('active_window');
localStorage.removeItem('last_update');
location.reload();
}
}
}
/********************************
** CHECK CURRENT WINDOW STATUS **
*********************************/
function check_current_window_status(test)
{
manage_crash();
if (PV_API)
{
var active_status = "Inactive";
var windows_list = get_main_windows_list();
var active_windows_list = get_active_windows_list();
if (windows_list.indexOf(localStorage.active_window) < 0)
{
//last actived windows is not alive anymore!
//remove_from_main_windows_list(localStorage.active_window);
//set the last added window, as active_window
localStorage.active_window = windows_list[windows_list.length - 1];
}
if (! document[PV_API.hidden])
{
//Window's page is visible
localStorage.active_window = current_window_id;
}
if (localStorage.active_window == current_window_id)
{
active_status = "Active";
}
if (active_status == "Active")
{
active_windows_list = add_to_active_windows(current_window_id);
}
else
{
active_windows_list = remove_from_active_windows(current_window_id);
}
console.log(test, active_windows_list);
var element_holder = document.getElementById("holder_element");
element_holder.insertAdjacentHTML("afterbegin", "<div>"+element_holder.childElementCount+") Current Windows is "+ active_status +" "+active_windows_list.length+" window(s) is visible and active of "+ windows_list.length +" windows</div>");
}
else
{
console.log("PageVisibility API is not supported :(");
//our INACTIVE pages, will remain INACTIVE forever, you need to make some action in this case!
}
localStorage.last_update = Date.now();
}
//check storage continuously
setInterval(function(){
check_current_window_status ();
}, time_period);
//initial check
check_current_window_status ();
</script>
</body>
</html>

How to make ondblclick event works on phone?

I want to achieve the double click event on a specific div like this:
<div id="divID" ondblclick = 'alert("double click!!");' >
it worked on the google chrome browser but when I open it with phone it didn't work, by the way the single click worked.
ps: i added this two things
<meta name="viewport" content="width=device-width, initial scale=1,user-scalable=no">
and this
body {
-ms-touch-action: manipulation;
touch-action: manipulation;}
but it didnt work!
I got the same issue. On touch devices, if you want to detect a double-tap gesture and you use the ondblclick event in most cases it will not work and also the problem is it will also fire an onclick. One of the solution is to implement a double tap detection pattern using the following code sample:
var doubletapDeltaTime_ = 700;
var doubletap1Function_ = null;
var doubletap2Function_ = null;
var doubletapTimer = null;
function tap(singleTapFunc, doubleTapFunc) {
if (doubletapTimer==null) {
// First tap, we wait X ms to the second tap
doubletapTimer_ = setTimeout(doubletapTimeout_, doubletapDeltaTime_);
doubletap1Function_ = singleTapFunc;
doubletap2Function_ = doubleTapFunc;
} else {
// Second tap
clearTimeout(doubletapTimer);
doubletapTimer_ = null;
doubletap2Function_();
}
}
function doubletapTimeout() {
// Wait for second tap timeout
doubletap1Function_();
doubleTapTimer_ = null;
}
And you can call it like
<div id="divID" onclick="tap(tapOnce, tapTwice)" >
tapOnce and tapTwice are your functions which will be called in respective cases. This solution will work on browsers too.
Reference
Here is the external function 'doubletap' which can be helpful:
/*
* jQuery Double Tap
* Developer: Sergey Margaritov (sergey#margaritov.net)
* Date: 22.10.2013
* Based on jquery documentation http://learn.jquery.com/events/event-extensions/
*/
(function($){
$.event.special.doubletap = {
bindType: 'touchend',
delegateType: 'touchend',
handle: function(event) {
var handleObj = event.handleObj,
targetData = jQuery.data(event.target),
now = new Date().getTime(),
delta = targetData.lastTouch ? now - targetData.lastTouch : 0,
delay = delay == null ? 300 : delay;
if (delta < delay && delta > 30) {
targetData.lastTouch = null;
event.type = handleObj.origType;
['clientX', 'clientY', 'pageX', 'pageY'].forEach(function(property) {
event[property] = event.originalEvent.changedTouches[0][property];
})
// let jQuery handle the triggering of "doubletap" event handlers
handleObj.handler.apply(this, arguments);
} else {
targetData.lastTouch = now;
}
}
};
})(jQuery);
Load jQuery Mobile into your project and try using taphold or some of the other mobile specific touch events that are available to you through that API.
Here's the jQuery Mobile documentation with all the events you can use: http://api.jquerymobile.com/category/events/
Here is the snippet for TS React users. Pass in the click event, so that double click is only invoked if the same element is clicked twice
import React from "react";
type CallBack = () => any;
type TapParams = { onSingleTap?: CallBack; onDoubleTap?: CallBack };
var DELTA_TIME_THRESHOLD_MS = 700;
var timer: NodeJS.Timeout | null = null;
var target: EventTarget;
export function tap(
e: React.MouseEvent,
{ onSingleTap, onDoubleTap }: TapParams
) {
if (timer == null) {
// First tap
onSingleTap?.();
timer = setTimeout(() => {
timer = null;
}, DELTA_TIME_THRESHOLD_MS);
} else {
// Second tap
if (e.target === target) {
onDoubleTap?.();
}
clearTimeout(timer);
timer = null;
}
target = e.target;
}
Usage
<div
onClick={(e) => tap(e, { onSingleTap, onDoubleTap })}
>Tap or doubletap</div>
Using only JavaScript
You can use "touchstart" event for a single touch,
but with calculating the time when he should click again
I used 400 (0.4s) as it's the longer duration between two touches
It's only an estimate, but it's still a reasonable time
let expired
let doubleClick = function () {
console.log('double click')
}
let doubleTouch = function (e) {
if (e.touches.length === 1) {
if (!expired) {
expired = e.timeStamp + 400
} else if (e.timeStamp <= expired) {
// remove the default of this event ( Zoom )
e.preventDefault()
doubleClick()
// then reset the variable for other "double Touches" event
expired = null
} else {
// if the second touch was expired, make it as it's the first
expired = e.timeStamp + 400
}
}
}
let element = document.getElementById('btn')
element.addEventListener('touchstart', doubleTouch)
element.addEventListener('dblclick', doubleClick)
In case of this error :
Unable to preventDefault inside passive event listener due to target being treated as passive.
event.preventDefault( ) not working if element = "document" or "document.body"
So the solution of that, you should have a full page div container :
let element = document.getElementById('container')
element.style.minWidth = '100vw'
element.style.minHeight = '100vh'
document.body.style.margin = '0px'
element.addEventListener('touchstart', elementTouch)
element.addEventListener('dblclick', doubleClick)

Event when user stops scrolling

I'd like to do some fancy jQuery stuff when the user scrolls the page. But I have no idea how to tackle this problem, since there is only the scroll() method.
Any ideas?
You can make the scroll() have a time-out that gets overwritten each times the user scrolls. That way, when he stops after a certain amount of milliseconds your script is run, but if he scrolls in the meantime the counter will start over again and the script will wait until he is done scrolling again.
Update:
Because this question got some action again I figured I might as well update it with a jQuery extension that adds a scrollEnd event
// extension:
$.fn.scrollEnd = function(callback, timeout) {
$(this).on('scroll', function(){
var $this = $(this);
if ($this.data('scrollTimeout')) {
clearTimeout($this.data('scrollTimeout'));
}
$this.data('scrollTimeout', setTimeout(callback,timeout));
});
};
// how to call it (with a 1000ms timeout):
$(window).scrollEnd(function(){
alert('stopped scrolling');
}, 1000);
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<div style="height: 200vh">
Long div
</div>
Here is a simple example using setTimeout to fire a function when the user stops scrolling:
(function() {
var timer;
$(window).bind('scroll',function () {
clearTimeout(timer);
timer = setTimeout( refresh , 150 );
});
var refresh = function () {
// do stuff
console.log('Stopped Scrolling');
};
})();
The timer is cleared while the scroll event is firing. Once scrolling stops, the refresh function is fired.
Or as a plugin:
$.fn.afterwards = function (event, callback, timeout) {
var self = $(this), delay = timeout || 16;
self.each(function () {
var $t = $(this);
$t.on(event, function(){
if ($t.data(event+'-timeout')) {
clearTimeout($t.data(event+'-timeout'));
}
$t.data(event + '-timeout', setTimeout(function () { callback.apply($t); },delay));
})
});
return this;
};
To fire callback after 100ms of the last scroll event on a div (with namespace):
$('div.mydiv').afterwards('scroll.mynamespace', function(e) {
// do stuff when stops scrolling
$(this).addClass('stopped');
}, 100
);
I use this for scroll and resize.
Here is another more generic solution based on the same ideas mentioned:
var delayedExec = function(after, fn) {
var timer;
return function() {
timer && clearTimeout(timer);
timer = setTimeout(fn, after);
};
};
var scrollStopper = delayedExec(500, function() {
console.log('stopped it');
});
document.getElementById('box').addEventListener('scroll', scrollStopper);
I had the need to implement onScrollEnd event discussed hear as well.
The idea of using timer works for me.
I implement this using JavaScript Module Pattern:
var WindowCustomEventsModule = (function(){
var _scrollEndTimeout = 30;
var _delayedExec = function(callback){
var timer;
return function(){
timer && clearTimeout(timer);
timer = setTimeout(callback, _scrollEndTimeout);
}
};
var onScrollEnd = function(callback) {
window.addEventListener('scroll', _delayedExec(callback), false);
};
return {
onScrollEnd: onScrollEnd
}
})();
// usage example
WindowCustomEventsModule.onScrollEnd(function(){
//
// do stuff
//
});
Hope this will help / inspire someone
Why so complicated? As the documentation points out, this http://jsfiddle.net/x3s7F/9/ works!
$('.frame').scroll(function() {
$('.back').hide().fadeIn(100);
}
http://api.jquery.com/scroll/.
Note: The scroll event on Windows Chrome is differently to all others. You need to scroll fast to get the same as result as in e.g. FF. Look at https://liebdich.biz/back.min.js the "X" function.
Some findings from my how many ms a scroll event test:
Safari, Mac FF, Mac Chrome: ~16ms an event.
Windows FF: ~19ms an event.
Windows Chrome: up to ~130ms an event, when scrolling slow.
Internet Explorer: up to ~110ms an event.
http://jsfiddle.net/TRNCFRMCN/1Lygop32/4/.
There is no such event as 'scrollEnd'. I recommend that you check the value returned by scroll() every once in a while (say, 200ms) using setInterval, and record the delta between the current and the previous value. If the delta becomes zero, you can use it as your event.
There are scrollstart and scrollstop functions that are part of jquery mobile.
Example using scrollstop:
$(document).on("scrollstop",function(){
alert("Stopped scrolling!");
});
Hope this helps someone.
The scrollEnd event is coming. It's currently experimental and is only supported by Firefox. See the Mozilla documentation here - https://developer.mozilla.org/en-US/docs/Web/API/Document/scrollend_event
Once it's supported by more browsers, you can use it like this...
document.onscrollend = (event) => {
console.log('Document scrollend event fired!');
};
I pulled some code out of a quick piece I cobbled together that does this as an example (note that scroll.chain is an object containing two arrays start and end that are containers for the callback functions). Also note that I am using jQuery and underscore here.
$('body').on('scroll', scrollCall);
scrollBind('end', callbackFunction);
scrollBind('start', callbackFunction);
var scrollCall = function(e) {
if (scroll.last === false || (Date.now() - scroll.last) <= 500) {
scroll.last = Date.now();
if (scroll.timeout !== false) {
window.clearTimeout(scroll.timeout);
} else {
_(scroll.chain.start).each(function(f){
f.call(window, {type: 'start'}, e.event);
});
}
scroll.timeout = window.setTimeout(self.scrollCall, 550, {callback: true, event: e});
return;
}
if (e.callback !== undefined) {
_(scroll.chain.end).each(function(f){
f.call(window, {type: 'end'}, e.event);
});
scroll.last = false;
scroll.timeout = false;
}
};
var scrollBind = function(type, func) {
type = type.toLowerCase();
if (_(scroll.chain).has(type)) {
if (_(scroll.chain[type]).indexOf(func) === -1) {
scroll.chain[type].push(func);
return true;
}
return false;
}
return false;
}

Long Press in JavaScript?

Is it possible to implement "long press" in JavaScript (or jQuery)? How?
(source: androinica.com)
HTML
Long press
JavaScript
$("a").mouseup(function(){
// Clear timeout
return false;
}).mousedown(function(){
// Set timeout
return false;
});
There is no 'jQuery' magic, just JavaScript timers.
var pressTimer;
$("a").mouseup(function(){
clearTimeout(pressTimer);
// Clear timeout
return false;
}).mousedown(function(){
// Set timeout
pressTimer = window.setTimeout(function() { ... Your Code ...},1000);
return false;
});
Based on Maycow Moura's answer, I wrote this. It also ensures that the user didn't do a right click, which would trigger a long press and works on mobile devices. DEMO
var node = document.getElementsByTagName("p")[0];
var longpress = false;
var presstimer = null;
var longtarget = null;
var cancel = function(e) {
if (presstimer !== null) {
clearTimeout(presstimer);
presstimer = null;
}
this.classList.remove("longpress");
};
var click = function(e) {
if (presstimer !== null) {
clearTimeout(presstimer);
presstimer = null;
}
this.classList.remove("longpress");
if (longpress) {
return false;
}
alert("press");
};
var start = function(e) {
console.log(e);
if (e.type === "click" && e.button !== 0) {
return;
}
longpress = false;
this.classList.add("longpress");
if (presstimer === null) {
presstimer = setTimeout(function() {
alert("long click");
longpress = true;
}, 1000);
}
return false;
};
node.addEventListener("mousedown", start);
node.addEventListener("touchstart", start);
node.addEventListener("click", click);
node.addEventListener("mouseout", cancel);
node.addEventListener("touchend", cancel);
node.addEventListener("touchleave", cancel);
node.addEventListener("touchcancel", cancel);
You should also include some indicator using CSS animations:
p {
background: red;
padding: 100px;
}
.longpress {
-webkit-animation: 1s longpress;
animation: 1s longpress;
}
#-webkit-keyframes longpress {
0%, 20% { background: red; }
100% { background: yellow; }
}
#keyframes longpress {
0%, 20% { background: red; }
100% { background: yellow; }
}
You can use taphold event of jQuery mobile API.
jQuery("a").on("taphold", function( event ) { ... } )
I created long-press-event (0.5k pure JS) to solve this, it adds a long-press event to the DOM.
Listen for a long-press on any element:
// the event bubbles, so you can listen at the root level
document.addEventListener('long-press', function(e) {
console.log(e.target);
});
Listen for a long-press on a specific element:
// get the element
var el = document.getElementById('idOfElement');
// add a long-press event listener
el.addEventListener('long-press', function(e) {
// stop the event from bubbling up
e.preventDefault()
console.log(e.target);
});
Works in IE9+, Chrome, Firefox, Safari & hybrid mobile apps (Cordova & Ionic on iOS/Android)
Demo
While it does look simple enough to implement on your own with a timeout and a couple of mouse event handlers, it gets a bit more complicated when you consider cases like click-drag-release, supporting both press and long-press on the same element, and working with touch devices like the iPad. I ended up using the longclick jQuery plugin (Github), which takes care of that stuff for me. If you only need to support touchscreen devices like mobile phones, you might also try the jQuery Mobile taphold event.
For modern, mobile browsers:
document.addEventListener('contextmenu', callback);
https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu
jQuery plugin. Just put $(expression).longClick(function() { <your code here> });. Second parameter is hold duration; default timeout is 500 ms.
(function($) {
$.fn.longClick = function(callback, timeout) {
var timer;
timeout = timeout || 500;
$(this).mousedown(function() {
timer = setTimeout(function() { callback(); }, timeout);
return false;
});
$(document).mouseup(function() {
clearTimeout(timer);
return false;
});
};
})(jQuery);
$(document).ready(function () {
var longpress = false;
$("button").on('click', function () {
(longpress) ? alert("Long Press") : alert("Short Press");
});
var startTime, endTime;
$("button").on('mousedown', function () {
startTime = new Date().getTime();
});
$("button").on('mouseup', function () {
endTime = new Date().getTime();
longpress = (endTime - startTime < 500) ? false : true;
});
});
DEMO
For cross platform developers (Note All answers given so far will not work on iOS):
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)
});
The Diodeus's answer is awesome, but it prevent you to add a onClick function, it'll never run hold function if you put an onclick. And the Razzak's answer is almost perfect, but it run hold function only on mouseup, and generally, the function runs even if user keep holding.
So, I joined both, and made this:
$(element).on('click', function () {
if(longpress) { // if detect hold, stop onclick function
return false;
};
});
$(element).on('mousedown', function () {
longpress = false; //longpress is false initially
pressTimer = window.setTimeout(function(){
// your code here
longpress = true; //if run hold function, longpress is true
},1000)
});
$(element).on('mouseup', function () {
clearTimeout(pressTimer); //clear time on mouseup
});
You could set the timeout for that element on mouse down and clear it on mouse up:
$("a").mousedown(function() {
// set timeout for this element
var timeout = window.setTimeout(function() { /* … */ }, 1234);
$(this).mouseup(function() {
// clear timeout for this element
window.clearTimeout(timeout);
// reset mouse up event handler
$(this).unbind("mouseup");
return false;
});
return false;
});
With this each element gets its own timeout.
This worked for me:
const a = document.querySelector('a');
a.oncontextmenu = function() {
console.log('south north');
};
https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oncontextmenu
You can use jquery-mobile's taphold. Include the jquery-mobile.js and the following code will work fine
$(document).on("pagecreate","#pagename",function(){
$("p").on("taphold",function(){
$(this).hide(); //your code
});
});
Most elegant and clean is a jQuery plugin:
https://github.com/untill/jquery.longclick/,
also available as packacke:
https://www.npmjs.com/package/jquery.longclick.
In short, you use it like so:
$( 'button').mayTriggerLongClicks().on( 'longClick', function() { your code here } );
The advantage of this plugin is that, in contrast to some of the other answers here, click events are still possible. Note also that a long click occurs, just like a long tap on a device, before mouseup. So, that's a feature.
I needed something for longpress keyboard events, so I wrote this.
var longpressKeys = [13];
var longpressTimeout = 1500;
var longpressActive = false;
var longpressFunc = null;
document.addEventListener('keydown', function(e) {
if (longpressFunc == null && longpressKeys.indexOf(e.keyCode) > -1) {
longpressFunc = setTimeout(function() {
console.log('longpress triggered');
longpressActive = true;
}, longpressTimeout);
// any key not defined as a longpress
} else if (longpressKeys.indexOf(e.keyCode) == -1) {
console.log('shortpress triggered');
}
});
document.addEventListener('keyup', function(e) {
clearTimeout(longpressFunc);
longpressFunc = null;
// longpress key triggered as a shortpress
if (!longpressActive && longpressKeys.indexOf(e.keyCode) > -1) {
console.log('shortpress triggered');
}
longpressActive = false;
});
In vanila JS if need to detect long-click after click released:
document.addEventListener("mousedown", longClickHandler, true);
document.addEventListener("mouseup", longClickHandler, true);
let startClick = 0;
function longClickHandler(e){
if(e.type == "mousedown"){
startClick = e.timeStamp;
}
else if(e.type == "mouseup" && startClick > 0){
if(e.timeStamp - startClick > 500){ // 0.5 secound
console.log("Long click !!!");
}
}
}
May need to use timer if need to check long-click while clicking. But for most case after release click is enought.
For me it's work with that code (with jQuery):
var int = null,
fired = false;
var longclickFilm = function($t) {
$body.css('background', 'red');
},
clickFilm = function($t) {
$t = $t.clone(false, false);
var $to = $('footer > div:first');
$to.find('.empty').remove();
$t.appendTo($to);
},
touchStartFilm = function(event) {
event.preventDefault();
fired = false;
int = setTimeout(function($t) {
longclickFilm($t);
fired = true;
}, 2000, $(this)); // 2 sec for long click ?
return false;
},
touchEndFilm = function(event) {
event.preventDefault();
clearTimeout(int);
if (fired) return false;
else clickFilm($(this));
return false;
};
$('ul#thelist .thumbBox')
.live('mousedown touchstart', touchStartFilm)
.live('mouseup touchend touchcancel', touchEndFilm);
You can check the time to identify Click or Long Press [jQuery]
function AddButtonEventListener() {
try {
var mousedowntime;
var presstime;
$("button[id$='" + buttonID + "']").mousedown(function() {
var d = new Date();
mousedowntime = d.getTime();
});
$("button[id$='" + buttonID + "']").mouseup(function() {
var d = new Date();
presstime = d.getTime() - mousedowntime;
if (presstime > 999/*You can decide the time*/) {
//Do_Action_Long_Press_Event();
}
else {
//Do_Action_Click_Event();
}
});
}
catch (err) {
alert(err.message);
}
}
You can use jquery Touch events. (see here)
let holdBtn = $('#holdBtn')
let holdDuration = 1000
let holdTimer
holdBtn.on('touchend', function () {
// finish hold
});
holdBtn.on('touchstart', function () {
// start hold
holdTimer = setTimeout(function() {
//action after certain time of hold
}, holdDuration );
});
like this?
target.addEeventListener("touchstart", function(){
// your code ...
}, false);

Categories

Resources