I have a webapp with an inner-pane inside an outer-window.
The user can do various touch operations, e.g. zoom-in in the inner-pane via 2-finger pinch, without zooming-in the outer-window, touch move to pan the inner window etc..
On Chrome on Android, the app works as expected.
But Safari on iOS device (iPad), the entire window sometimes scales when quickly double-toching on the border-line of the inner window.
I found out that I can prevent this side effect by adding an event listener on the touchend event.
window.addEventListener('touchend', onTouchEnd5, {capture: false, passive: false});
and calling event.preventDefault() in the binded function.
async function onTouchEnd5( event ) {
console.log('BEG onTouchEnd5');
event.preventDefault();
};
But this causes another side effect where touch-clicks on buttons are not intercepted.
I tried adding eventlisteners on other events that are possibly trigerred by the default behavior, e.g. click, dblclick, touchcancel, fullscreenchange, fullscreenerror, resize, onscroll
The idea was to try and prevent the default behaviour for these events in case that the side effect is caused there.
(the rational was that iphone/ipad can trigger unexpected events).
From the listed events above, the event onscroll is trigerred.
In the binded function to this event, I added preventDefault():
function onScroll5( event ) {
console.log('BEG onScroll5');
event.preventDefault();
};
But the I can still cause the entire window to rescale by quickly double-touching on the border-line of the inner window.
How can I prevent the entire window from scaling in iOS when double touching?
Note:
The scaling of the entire window is the latest observation.
Before that, I found that the entire window scales when e.g. doing a twon-finger pinch in another section of the window.
The solution in this case, was to add preventDefault on another event listener binded function.
Given that there are multiple events in the app, a more general question is:
Is there a way to prevent the entire window from scaling alltogether, in eny event?
Thanks
I've found a bit of a hacky solution.
Make a page called /mobile, or use / and put all your content on a page called /maincontent and use that URL in the next step.
In that page, add an iframe, like this:
<iframe src='/ (or "maincontent")' style='width:100vw;height:100vh;top:0;left:0;position:fixed;border:none;' frameborder='0'></iframe>
I know it's a bit hacky, but it works for me on iOS 14.
I found out the solution.
I had the following DOM structure:
<body>
<div id="div1">
<div id="div2">
<div id="div3">
<canvas>...</canvas>
</div>
</div>
<div id="div4">
<div id="div5"><button></div>
</div>
</div>
</body>
I initially tried to add event.preventDefault() in onTouchStartDiv3(), which listens to events in the canvas element (the closest upstream element with event listener is div3).
This solved the problem of double-touch in the border but caused another problem:
onTouchMoveDiv3() was called immediately (without even moving the finger), which caused other side-effects. (I have some state machine in the level, that depends on the onTouchStartDiv3(), onTouchMoveDiv3(), onTouchEndDiv3()).
I then tried to add event listeners at the window/document levels (onTouchStartWindow(), onTouchStartDocument()) and call event.preventDefault() in them.
This again solved the problem of double-touch in the border but caused another problem:
The button (div5), stopped responding to touch events.
The reason was that, because there is no specific event listener on the button, the event was intercepted higher up, and default behaviour was applied.
Because the window/document event listener called event.preventDefault(), this behaviour was prevented (which stopped the button from responding to touch events).
Finally, I added event listener at the touched border level (div2).
Inside onTouchStartDiv2() I compare event.currentTarget (div2), to event.target where the event originated.
If the event.target is div2 then the event originated from clicking on the border, so I apply event.preventDefault() to prevent the side-effect of resizing the entire image on iOS.
If the event.target is not div2 then the event originated from clicking, e.g. in the canvas, so I do not apply event.preventDefault() to maintain the state machine.
With this logic, I solved the problem:
when touching the canvas element (div3 is the closest upstream element with event listener), the regular behaviour was applied.
when touching the border element (div2), event.preventDefault() was applied to the event listener, and prevent the default behaviour of resizing the entire window (which I wanted to disable).
when touching the button element (div5), the regular behaviour was applied so the button responded to touch events.
Related
what is the best way to catch and handle a click event on "anything except specific DOM-node(s)" in a React app?
The handler of the click event is the easy part: this can be any method.
The registration of the event, and the trigger to invoke the handler, is the hard part.
There is no clean way to capture a "clicks outside ...." event.
There are however various (HTML/ CSS/ Javascript) tricks you could apply:
If it is a modal page/ popup, you could also render a full page background rectangle (e.g. slightly transparent grey), which is in front of the whole page, but behind the popup. Add a click-event-listener to this background to remove the modal + the grey background.
Another method is to use the focusout javascript event on your top-react component:
the top HTML component rendered by react should be able to get focus (needs to be an <a> or similar HTML, or - somewhat less clean - needs a tabindex=... to work)
give the element focus as soon as it is rendered (inside componentDidMount()`)
add a focusout event listener, which triggers the handler to do something with the click outside.
The focusout event is fired as soon as the component no longer has focus:
- if a child of the component gets focus (e.g. you click something inside the component) focusout is also fired: usually no problem for menu's, but undesired for popups with forms
- the focusout is also fired if the user presses TAB.
There's no React-specific way to do this; all React event handlers are tied to the component they're set on. The best way to accomplish this depends on the details of what you need to get done, but a fairly straightforward way to address this would be to add a delegated click handler to the body element, or the closest ancestor element that includes the area you want to capture clicks from. You'd attach this event handler either on the component's componentDidMount() or whenever it becomes relevant, for example, after toggling the component's state so that it shows a dropdown menu.
Attach the event handler however you normally would – element.addEventListener or jQuery's $().on or what-have-you - and evaluate the event target when it fires to determine whether you need to execute your custom logic.
Simple example, without jQuery:
componentDidMount() {
document.body.addEventListener('click', (e) => {
if (e.target !== [your dom node]) {
// do something
}
}
}
Attaching a single event handler on the body element shouldn't pose any performance issues, but best practice for most cases where you'd use something like this would be to remove the event handler when it's no longer needed.
When my overlay comes up, everything works well, but I added some code to close out the overlay, but this code gets triggered even when I'm just clicking my arrows. The following is the code that's being triggered, which is fine when I'm not clicking the arrows to change the image. But when I click the arrows, the background which is the overlay is also being trigger, so the image is changing but the overlay is also hiding.
$('#overlay').click(function() {
$(this).fadeOut('slow');
});
How can I be able to use the arrows without it also clicking on the background overlay? If you open up the project, you will see what I'm saying.
To open the project:
https://htmlpreview.github.io/?https://github.com/rodriguesandrewb/photo_gallery_v1/blob/master/index.html
To open the repository:
https://github.com/rodriguesandrewb/photo_gallery_v1
You want to use event.stopPropagation(): https://api.jquery.com/event.stoppropagation/
This prevents the event from bubbling (being triggered by other elements)
Your outter most element is #overlay. It means that no matter where you click you'll be always clicking on your #overlay element. That is way your callback is being always triggered and closing your image.
To fix your problem and make your image close only when clicking on it you could use:
$('#changeImage').click(function() {
$(this).closest('#overlay').fadeOut('slow');
});
Ok, there's a ton of code to sort out, so I'm guessing your overlay is
<div id="overlay" style="display: block;"></div>
and your event.target is deep down inside this:
<div class="mainCenter">
<div class="container">
<div id="topFixed">
<input type="text" id="search" placeholder="Search">
</div>
<ul id="gallery">
.......
I'm not 100% sure where your event.target is, (the element you want to click and not everything else). But it's safe to assume that after you click your intended button, the event continues to bubble up the event chain. The event chain is basically your event.target's ancestors which includes#overlay` which is at the very top of the event chain.
To prevent event bubbling (btw bubbling is the default behavior but in instances such as your's it's not desired.) try placing stopPropagation() after or inside at the end of your event handler.
I wish I could be more specific as to where and how to apply this code as it pertains to your source, but you didn't provide the specific areas that concern your eventListeners, eventHandlers, etc...
The #overlay is used in this example but I suggest you use the event.target parent instead. The purpose of this code is to accept an event like 'click' on an element (i.e. button) or multiple elements (i.e. buttons) through their mutually shared parent. That's one place to click for potentially several different buttons. At first you'd think that's non-sense and you'd say, "Sure that button is clicked because the parent was clicked, but now everything the parent is chained to will trigger everything else."
That would be correct except we have stopPropagation(); at the very end of your eventHandler. That will stop propagation of the event bubbling back up the event chain, so there's no more rogue triggers lighting up everywhere. Rogue Triggers® sounds like a great band name. :P
For details and a much better explanation: http://www.kirupa.com/html5/handling_events_for_many_elements.htm
var overlay = document.querySelector("#overlay");
theParent.addEventListener("click", doSomething, false);
function doSomething(e) {
if (e.target !== e.currentTarget) {
var clickedItem = e.target.id;
alert("Hello " + clickedItem);
}
e.stopPropagation();
}
I want to capture every mouse event and I tried to add event listeners to FlexGlobals.topLevelApplication. However there are some cases (like Flex's Menu class) when the clicks aren't captured - probably somewhere the event propagation is stopped. For example in SystemManager.as I found this:
addEventListener(MouseEvent.MOUSE_DOWN, mouseEventHandler, true, 1000);
...which seems to override my listener. Also I tried to add Listeners with priority int.MAX_VALUE but with no success.
So my question is - how can I capture all mouse clicks, without worrying about my events being stopped somewhere? Maybe some javascript hack? Or maybe to add the event listeners somewhere where there is no chance for them to be canceled? Thanks.
You could try adding the event handler to stage with capture:
systemManager.stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseEventHandler, true, Integer.MAX_VALUE);
I am having a problem with my Javascript code for Android tablets.
Suppose I have grid made of several div tags. with class "box";
Now I bind event handlers for all these divs in the grid.
When the touchstart (mousedown) event occurs and I move the cursor to some other div in the grid (without releasing the cursor) and then release the cursor (touchend) on this current div. When I tried to alert the id of this current div (i.e. touchend div), the alert shows the id of the div where the "touchstart" has occurred.
$(".box").bind('touchstart',function () {alert($(this).attr("id"))});
$(".box").bind('touchend',function () {alert($(this).attr("id"))});
Actually this is my first program for the Android tablet. So I need help for this.
This is expected and it would be be very confusing if it did anything else. If the element it ended up on did not have a registered listener you would never get the touchEnd event at all.
You can see where the touch went by looking at the coordinate properties of the touches and you can track it in progress with touchmove.
Don't really get your question. Are you trying to get the alert to display the div id of where you mousedown? You will need to store the id in the touchstart event in a variable and display the value when touchend has occured.
I have designed a website with a menu that is initially invisible. When the user clicks on a button, the menu becomes visible. There are two ways for the user to hide the now visible menu:
Click the button that caused the menu to become visible
Click anywhere on the web page that isn't the menu
The way I have coded the second option is to tie an onclick event to the window element, and have it compare where the user clicked to the menu's position to determine if the menu should be hidden. This works great in Firefox and Safari, but it fails in Mobile Safari.
I noticed that the window onclick event only fires when I click on another element with an onclick event already assigned. If I click on an element with no event(s) assigned, the window's onclick event never fires. If I click on the button which displays the menu, it fires along with the event tied to the button.
Is it possible to assign events to the window element in Mobile Safari?
I'v been encountering this same problem. Here is what worked for me. (Note: I am working within a Modernizr and jQuery context)
First, I add a custom Modernizr class using Modernizr's addTest Plugin API to test for iOS, which will add the class appleios or no-appleios accordingly.
Because in my research the body seems to fire events on it's own agenda, I am taking a little precaution by wrapping all the document's content with an element in an iOS context. Then I add an event handler to this element.
$(".appleios body").wrapInner('<div id="appleios-helper" />');
$("#appleios-helper").bind("mouseup", function(){return;});
What was suggested earlier in this thread is using void(0). I did some quick testing, and found that void(0) as the event just wasn't causing touches on the body to be recognized. When I plugged in my own "empty" function in the form of function(){return;} things started working.
This all hinges on the fact that no events are fired in Mobile Safari unless the element explicitly has events to fire (Safari Web Content Guide.) By inserting this empty event on the wrapper, things will bubble up to the body.
If you're doing strait JavaScript with none of these libraries, the same effect could be achieved in the HTML markup
<html>
...
<body>
<div id="appleios-helper" onmouseup="function(){return;}">
...
</div>
</body>
</html>
This worked for me to hide tooltips when touching anywhere on the document's body. Your mileage may vary.
Simply adding the dummy onclick handler to the html body works for me:
<body onclick="void(0)">
Note that I am using usual live event handlers as shown below:
function liveHandler( event ) {
var target = event.target; ...}
window.addEventListener(evtype, liveHandler, true);
// evtype such as 'mousedown' or 'click'
// we use the capturing mode here (third parameter true)
This is an old question, but I struggled with the same thing today.
I found that using touchstart event works.
I solved it like this:
var isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
// Do touch related stuff
$(document).on('touchstart', function (event) {
// Do stuff
});
} else {
// Do non-touch related stuff
$(document).on('click', function () {
// Do stuff
});
}
You could just add onclick="void(0);" to some <div> that covers the whole page so that no matter what, you are always clicking on an element that has an onclick event. Not a great solution, though.
I'd prefer not having the onclick event be tied to the window. Why don't you create a container <div> that has that event on it. Then handle it just like you currently are.
You can also:
$('body').css('cursor', 'pointer');
No idea what those "engineers" at Apple are doing. LOL.
This has problems though. You wouldn't want to do this on every touch device. Only touch devices that don't also have a pointing device (Laptops with Touch Screens, for example).
Source: http://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
The conclusion of the article is this:
So I don’t understand why all this is the case, but it most certainly is the case. If you’re having bubbling problems, just add an empty-function event handler anywhere between the body and the element, and you’re set to go. But it shouldn’t be necessary.