To everyone, I have a problem in my ReactJs app with the onDoubleClick() function assigned to a component, on a desktop pc my function works correctly, but on an Android mobile phone, (iPhone works fine) with the screen in vertical the function doesn't work, the screen in my phone doesn't recognize the double click as my assigned function, instead, it makes zoom in or zoom out, I want to prevent this behavior with my function.
Below is part of my code, and you can see the app in estebmaister.github.io/react-calendar/ or in the public repo with the same name.
onDoubleClick = (event) => {
this.setState({
popperAnchor: Boolean(this.popperAnchor) ? null : event.currentTarget,
});
};
render() {
return (
<td
key={"d" + this.props.day}
className={`calendar-day ${this.props.currentDay}`}
onDoubleClick={this.onDoubleClick}
>
I hope you can give me a clue to fix this problem, thanks.
this solution works for when declaring components using functions rather than classes:
....some code...
const [waitingClick, setWaitingClick] =
useState(null);
const [lastClick, setLastClick] = useState(0);
const processClick = (e) => {
if(lastClick&&e.timeStamp - lastClick < 250 &&
waitingClick){
setLastClick(0);
clearTimeout(waitingClick);
setWaitingClick(null);
console.log('double click')}
else{
setLastClick(e.timeStamp);
setWaitingClick(setTimeout(()=>{
setWaitingClick(null);
}, 251))
console.log('single click')
}
}
....some code...
<component onClick={(e)=>processClick(e)}></>
Here is my solution for double click. Please note that onDoubleClick does not work in mobile version of almost all browsers. And you need to use onTapStart and so on. But I would like to introduce a simple work around that I have tested in Chrome, FireFox and Safari and it works perfect. I made a double click via onClick event for a img tag.
render() => {
this.watingClick = null; // a reference to timeout function
this.lastClick = 0; // a watchdog for difference between 2 clicks
return ( <img
onClick={(e) => {
if (
this.lastClick &&
e.timeStamp - this.lastClick < 250 &&
this.watingClick
) {
this.lastClick = 0;
clearTimeout(this.watingClick);
console.log("Do the steps to respond double click");
this.watingClick = null;
} else {
this.lastClick = e.timeStamp;
this.watingClick = setTimeout(() => {
this.watingClick = null;
console.log("Do the steps to respond single click");
}, 251);
}
}}
/>
);
}
Here, I assumed time difference between 2 clicks is 250 mili seconds, you can increase it even to 300, however, decreasing this value in most browsers does not work.
Related
I have a tag element for a web page. when clicking once, one logic is executed, when clicking twice, another. However, the DOM only responds to one click and fires immediately, no matter how quickly you try to fire a double click event.
How do I use these two events on one element?
export const createTag = (name) => {
const tag = document.createElement('span');
tag.innerHTML = name;
tag.addEventListener('dblclick', () => {
tags.removeChild(tag);
storeTags.splice(storeTags.indexOf(name), 1);
});
tag.addEventListener('click', () => {
search.value = tag.innerHTML;
const keyboardEvent = new KeyboardEvent('keypress', {
code: 'Enter',
key: 'Enter',
charCode: 13,
keyCode: 13,
view: window,
bubbles: true,
});
storeTags.splice(storeTags.indexOf(name), 1);
storeTags.unshift(name);
search.dispatchEvent(keyboardEvent);
});
return tag;
};
The basic idea to achieve this would be to add some small delay to the click operation, and cancel that if a dblclick is received. This does lead to reduced responsiveness of your UI, as your application is now having to explicitly distinguish between single and double clicks in a kind of "wait and see" approach. This can also be unreliable as the user may set any length of time as their double-click threshold in their device's Accessibility settings.
However, if these issues are deemed not to be enough of a concern, the "best of the worst" way to do this would be to manually implement double-click checks.
export const createTag = (name) => {
const tag = document.createElement('span');
tag.innerHTML = name;
const clickHander = () => {
search.value = tag.innerHTML;
const keyboardEvent = new KeyboardEvent('keypress', {
code: 'Enter',
key: 'Enter',
charCode: 13,
keyCode: 13,
view: window,
bubbles: true,
});
storeTags.splice(storeTags.indexOf(name), 1);
storeTags.unshift(name);
search.dispatchEvent(keyboardEvent);
};
const dblClickHandler = () => {
tags.removeChild(tag);
storeTags.splice(storeTags.indexOf(name), 1);
};
let clickTimer;
tag.addEventListener('click', () => {
if( clickTimer) {
clearTimeout(clickTimer);
dblClickHandler();
clickTimer = null;
}
else {
clickTimer = setTimeout(() => {
clickHandler();
clickTimer = null;
}, 500);
}
});
return tag;
};
Adjust the timer as you feel appropriate. Smaller numbers will lead to more responsive clicks, but will make double-clicking harder for people with reduced mobility.
I would really strongly recommend using a different UI here. Overloading clicks in this way is a really bad idea in general.
How to add keyboard navigation to this ant design full screen images as of now it is accessible only through click of pointer
This component adds a bit of bloat and I really can't see a callback for after it loads a preview. However, that being said... This code does what you want. It's pretty fragile, and might break on updates of the module.
Update: Because I felt like I was taking something away with this answer, I fixed it now so all arrows are in sync with keyboard actions.
*Final Update I fixed an issue where having more than one gallery would break things. Should be perfect now.
Here's a CodeSandbox Example
// Must be assigned to an onClick event
function onClickImage(e, parentContainer = null) {
let currentImgSRC;
if (e.currentTarget) {
currentImgSRC = e.currentTarget.querySelector("img").src;
// Keep track of our parent container, as not to include other containers
parentContainer = e.currentTarget.parentElement;
} else {
currentImgSRC = e;
}
let PreviewWrapDoms = document.getElementsByClassName(
"ant-image-preview-wrap"
);
let ImgPreviewBodyDom = document.getElementsByClassName(
"ant-image-preview-body"
)[0];
// I don't see any kind of callback accessible for when the target
// gets rendered, so we simply wait until the time is right.
setTimeout(function () {
// Previous viewers aren't self clearing, so I added that in.
// Otherwise this breaks with multiple galleries
if (typeof ImgPreviewBodyDom === "undefined" || PreviewWrapDoms.length > 1 ) {
for (let WrapIndex = 0; WrapIndex < PreviewWrapDoms.length; WrapIndex++) {
if ( window.getComputedStyle(PreviewWrapDoms[WrapIndex]).display === "none") {
PreviewWrapDoms[WrapIndex].parentElement.parentElement.remove();
}
}
onClickImage(currentImgSRC, parentContainer);
return;
}
let ImgPreviewDom = ImgPreviewBodyDom.getElementsByClassName(
"ant-image-preview-img"
)[0];
let LeftArrowDom = ImgPreviewBodyDom.getElementsByClassName(
"ant-image-preview-switch-left"
)[0];
let RightArrowDom = ImgPreviewBodyDom.getElementsByClassName(
"ant-image-preview-switch-right"
)[0];
// Assigning event listeners for the left and right arrows.
if (LeftArrowDom.getAttribute("listener") !== "true")
LeftArrowDom.addEventListener("click", (e) => {
e.preventDefault();
onClickImage(
LeftArrowDom.getAttribute("data-img-prev"),
parentContainer
);
});
LeftArrowDom.setAttribute("listener", "true");
// Now the right arrow
if (RightArrowDom.getAttribute("listener") !== "true")
RightArrowDom.addEventListener("click", (e) => {
e.preventDefault();
onClickImage(
LeftArrowDom.getAttribute("data-img-next"),
parentContainer
);
});
RightArrowDom.setAttribute("listener", "true");
// Set these previous and next img sources to the current
// for the edge cases that may be there is no next or previous
let previousImgSRC = currentImgSRC;
let nextImgSRC = currentImgSRC;
let ImgDoms = parentContainer.getElementsByClassName("ant-image-img");
// Cycle through the dom in the container to see which we are
for (let ImgDomIndex = 0; ImgDomIndex < ImgDoms.length; ImgDomIndex++) {
if (currentImgSRC === ImgDoms[ImgDomIndex].src) {
if (ImgDomIndex > 0) { //If greater than zero, we know there's a previous
previousImgSRC = ImgDoms[ImgDomIndex - 1].src;
LeftArrowDom.setAttribute("data-img-prev", previousImgSRC);
LeftArrowDom.classList.remove(
"ant-image-preview-switch-left-disabled"
);
} else {
LeftArrowDom.classList.add("ant-image-preview-switch-left-disabled");
}
if (ImgDomIndex + 1 < ImgDoms.length) { // if the next index doesn't exist, no next
nextImgSRC = ImgDoms[ImgDomIndex + 1].src;
LeftArrowDom.setAttribute("data-img-next", nextImgSRC);
RightArrowDom.classList.remove(
"ant-image-preview-switch-right-disabled"
);
} else {
RightArrowDom.classList.add(
"ant-image-preview-switch-right-disabled"
);
}
// Once we know where we are in the index, we can set the preview
// Image source to our desired result.
ImgPreviewDom.src = currentImgSRC;
// We break here because once we know our index, we don't need more
break;
}
}
// checks for keydown events on the dom.
this.onkeydown = (e) => {
e = e || window.event;
switch (e.keyCode) {
// we use recursion here to keep everything contained nicely.
case 37:
onClickImage(previousImgSRC, parentContainer);
break;
case 39:
onClickImage(nextImgSRC, parentContainer);
break;
default:
break;
}
};
}, 500);
}
Here's the code you'll need for an Image preview group:
<Image.PreviewGroup>
<Image
onClick={onClickImage}
width={200}
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
/>
<Image
onClick={onClickImage}
width={200}
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
/>
</Image.PreviewGroup>
This Link can help you in similar way actually this is a image gallery that uses onclick event to open images you can use similar concept to get response.
https://www.codespeedy.com/how-to-create-a-tab-image-gallery-html-css-and-javascript/
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
I have applied this sort of line in order to make the component UI updated everytime. But in some other cases it start to refreshing the page event if it should reuse the route.
How can we overcome this issue?
Actually in my application there are three tabs in left panel. In each tab there are some listings clicking on list items opens the content on right panel. But in one of the listing there is a common UI that is getting open on some list item, but the problem is that when we don't apply above sort of code then the UI is not getting updated. But if we apply the code then the UI is updated everytime we click on other list item. But the problem is that when we apply this code it start to refresh the page everytime we click on other list in different tabs also, that should not be the case.
If we apply this code this.router.routeReuseStrategy.shouldReuseRoute = () => false; then how can we revert this functionality under this.router?
To take less risks I'm just reverting it back to what it was once the reload is done:
refresh() {
const prev = this.router.routeReuseStrategy.shouldReuseRoute;
const prevOSN = this.router.onSameUrlNavigation;
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.router.onSameUrlNavigation = 'reload';
this.router.navigate([this.router.url]);
setTimeout(() => {
this.router.routeReuseStrategy.shouldReuseRoute = prev;
this.router.onSameUrlNavigation = prevOSN;
}, 0);
}
I have the same issue, I changed that line for this:
// override the route reuse strategy
this.router.routeReuseStrategy.shouldReuseRoute = function () {
return false;
};
this.router.events.subscribe((evt) => {
if (evt instanceof NavigationEnd) {
// trick the Router into believing it's last link wasn't previously loaded
this.router.navigated = false;
// if you need to scroll back to top, here is the right place
window.scrollTo(0, 0);
}
});
I don't even know if this works well or do the same thing.
private saveRouterStrategyReuseLogic: any;
ngOnInit() {
// Save logic
this.saveRouterStrategyReuseLogic = this.router.routeReuseStrategy.shouldReuseRoute;
this.router.routeReuseStrategy.shouldReuseRoute = (future, curr) => { return false; };
}
ngOnDestroy() {
this.router.routeReuseStrategy.shouldReuseRoute =
this.saveRouterStrategyReuseLogic;
}
Based on this answer, I'm trying to create a Vue slideToggle component using transition.
The slideToggle is a classic paradigm in height animation. I've been successful so far...
I don't want to set a fixed max-height or height, I want the height to be dynamic.
My animation is working properly when displaying and hiding. The problem is in canceling while displaying or hiding.
How to handle the #enter-cancelled and the #leave-cancelled hooks? I'm new to vue transitions :)
I put my code inside this CodeSandBox: https://codesandbox.io/s/vue-template-3b7oj
I don't know if this helps you, but try this:
declare a new variable:
data() {
return {
height: null,
toggling: false
};
},
when the open or close function start, verify if toggling is true, if yes, just cancel, like this:
enter(el) {
if (this.toggling) return;
this.toggling = true;
this.height = el.offsetHeight;
el.style.overflow = "hidden";
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
el.style.marginTop = 0;
el.style.marginBottom = 0;
setTimeout(() => {
el.style.transitionProperty = `height, margin, padding`;
el.style.transitionDuration = this.duration + "ms";
el.style.height = this.height + "px";
el.style.removeProperty("padding-top");
el.style.removeProperty("padding-bottom");
el.style.removeProperty("margin-top");
el.style.removeProperty("margin-bottom");
this.toggling = false;
});
},
Will be something like this:
https://codesandbox.io/s/vue-template-78n7t?fontsize=14
Maybe i broke your code, sorry, but will you get the idea.
As per the offical documentation Javacript transition hooks
the #leave-cancelled is only available with v-show, where are in your sample code you are using v-if, if you change this you will be able to capture the #leave-cancelled hook,#leave-cancelled and #enter-cancelled are triggered when enter or leave are interrupted, say you press the toggle button while opening as well as pressing the button while its closing.
Vue-Transition-Cancel
tl;dr
leave event cancels not yet called enter
enter cancels not yet called leave
cancel state is stored in
el._enterCb.cancelled
el._leaveCb.cancelled
analysis
Consider:
const cb = el._enterCb = once(() => {
if (expectsCSS) {
removeTransitionClass(el, toClass)
removeTransitionClass(el, activeClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, startClass)
}
enterCancelledHook && enterCancelledHook(el)
} else {
afterEnterHook && afterEnterHook(el)
}
el._enterCb = null
})
source: _enterCb
So a naive solution to cancel #enter is
el => {el._enterCb.cancelled = true; done()}
This is what actually happens when one triggers leave
// call enter callback now
if (isDef(el._enterCb)) {
el._enterCb.cancelled = true
el._enterCb()
}
source: leave
Same applies to:
const cb = el._leaveCb = once(() => {
if (el.parentNode && el.parentNode._pending) {
el.parentNode._pending[vnode.key] = null
}
if (expectsCSS) {
removeTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveActiveClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, leaveClass)
}
leaveCancelled && leaveCancelled(el)
} else {
rm()
afterLeave && afterLeave(el)
}
el._leaveCb = null
})
source: _leaveCb
One can check for possible assignments:
https://github.com/vuejs/vue/search?q=_leaveCb&unscoped_q=_leaveCb
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)