Why touchmove event is not fired after DOM changes? - javascript

While moving my application from mouse to touch events I noticed some strange behaviour. Basically, touchmove stops working after DOM changes. Mouse events work fine in the same situation. I tested it with chrome developer tools as well as firefox's. They seem to agree on results. Is it a bug or am I missing something?
I created very simple code example to demonstrate that the problem is not connected to any frameworks or libs I use. I also found seemingly related question which unfortunately contains no solution.
Touch demo:
window.addEventListener("touchmove", onTouchMove, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("touchstart", onTouchStart)
})
function onTouchMove(event) {
console.log("touch move")
}
function onTouchStart(event) {
console.log("touch start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
Mouse demo:
window.addEventListener("mousemove", onMouseMove, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("mousedown", onMouseDown)
})
function onMouseMove(event) {
console.log("mouse move")
}
function onMouseDown(event) {
console.log("mouse start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
One continuous drag gesture starting from red square should cause 1) 'start' message in the log, 2) disappiaring of that square, which is the DOM change in this case 3) sequence of 'move' messages in the log. It is so in mouse demo, but in touch demo there are no 'move' events after square disappears.

This is an intended behaviour if your element is deleted.
According to the docs, if you delete an element, the events will still be targeted at it, and hence won't necessarily bubble up to the window or document anymore.
So there are two solutions if you want to delete the element. You can modify the "remove" method so that it would only hide the element until the touch process ends, or you can attach events to the target itself.
Here is an example, you can see the window touchmove events do not appear, while the element touchmove events appear even after the element's removal.
window.addEventListener("touchmove", onTouchMoveWindow, {passive: false})
document.addEventListener('DOMContentLoaded', function(){
var elem = document.getElementById("nice");
console.log(elem)
elem.addEventListener("touchstart", onTouchStart)
elem.addEventListener("touchmove", onTouchMoveElement)
})
function onTouchMoveWindow(event) {
console.log("touch move window")
}
function onTouchMoveElement(event) {
console.log("touch move element")
}
function onTouchStart(event) {
console.log("touch start")
var elem = document.getElementById("nice")
elem.remove()
}
<!DOCTYPE html>
<html>
<body style="width: 100%; height: 100%; background-color: yellow">
<div style="position: absolute; width: 100px; height: 100px; background-color: red; left: 100px; top: 100px" id="nice"></div>
</body>
</html>
Related questions:
Touchmove event stops triggering after any element is removed from dom
Touch Move event don't fire after Touch Start target is removed
touchmove events stop after replacing innerHTML

Related

How to propagate event from element with higher z-index to elements under excluding click event?

I have a component which has markers in it. Marker is a styled div with icon. Markers have click, mouseenter and mouseleave events. When mouse enters tooltip appears. On top of markers I can place other element to cover them. That element has higher z-index. I still want to be able to hover over (mouseenter, mouseleave) over lower z-index elements (markers) while preventing click event on them when they are covered. Is there any solution to pass only few or exclude only some event from propagation on higher z-index element?
<!DOCTYPE html>
<style>
#elmHigherZindexID {
width: 100px;
height: 100px;
position: absolute;
background-color: chartreuse;
z-index: 1000;
}
#elmLowerZindexID {
width: 100px;
height: 100px;
position: absolute;
background-color: cornflowerblue
}
</style>
<body>
<div id="elmHigherZindexID">HIGH</div>
<div id="elmLowerZindexID">LOW</div>
</body>
<script>
let highElmRef = document.getElementById('elmHigherZindexID');
let lowElmRef = document.getElementById('elmLowerZindexID');
highElmRef.addEventListener('click', highEventHandler);
highElmRef.addEventListener('mouseenter', highOtherEventHandler);
lowElmRef.addEventListener('mouseenter', lowEventHandler);
function highEventHandler(event) {
event.stopPropagation();
console.log('high', event);
}
function highOtherEventHandler(event) {
event.stopPropagation();
console.log('high', event);
const cusEvent = new MouseEvent('mouseenter', {
view: window,
bubbles: true,
cancelable: true
});
lowElmRef.dispatchEvent(cusEvent);
}
function lowEventHandler(event) {
event.stopPropagation();
console.log('low', event);
}
</script>
</html>

How to make click event trigger from a mousedown that was triggered manually?

I currently have a popped-up context menu. This popup gets closed when you click outside of it.
This is done with a div behind the popup that takes up the whole window, and listens for the mousedown event.
I need this mouse event to be sent to elements on the page behind the layer, so I call trigger() on those.
function onLayerMouseDown(e) {
layer.hide();
$("#menu").hide();
target = document.elementFromPoint(e.pageX - $(window).scrollLeft(), e.pageY - $(window).scrollTop());
if (target != null)
$(target).trigger(e);
This works well for the mousedown and mouseup events, but the click event which would come when the mouse presses and releases without leaving the element is never sent.
How can I make it so the click event gets sent too?
See this fiddle for an example with the layer. Click the text behind while the menu is open and see the console.
The problem is easy to understand, but the solution will depend on your requirements. Once the mousedown event is sent, the first thing that the script does is to hide the layer. This means that it will no longer detect mouse events. This means that no click event is ever emitted, neither for the layer (the click event would need a mouseup inside the same element) nor for the inside (the mousedown event received is a copy and comes from layer). It is important to notice that the click event is managed by the browser. You cannot trigger a click by just sending a mousedown and then a mouseup. That also explains why inside receives a mouseup event, as this event only happens after layer has disappeared.
Regarding how to solve it, you might:
Set the layermousedown function to handle the click event. With this design, it would mean that mousedown and mouseup are not captured.
Change the design. The idea would be to set the pointer-events of layer to none:
$("#inside").on("mousedown", (e) => {
console.log("inside mousedown (triggered manually from the layer)");
});
$("#inside").on("click", (e) => {
console.log("inside click (should happen right before mousup, if the mouse stayed inside the element");
});
$("#inside").on("mouseup", (e) => {
console.log("inside mouseup (triggered automatically when releasing mouse button)");
});
$("#button").click(() => {
createlayer();
return false;
});
$(document).on('click', layermousedown);
$("#menu").click(() =>{return false});
function layermousedown(e) {
$("#layer").hide();
$("#menu").hide();
$("#button").prop('disabled', false);
}
function createlayer() {
$("#button").prop('disabled', true);
$("#layer").show();
$("#menu").show();
console.log("added the invisible layer");
console.log("_______________________");
}
createlayer();
#layer {
height: 100vh;
width: 100vw;
display: block;
position: fixed;
z-index: 1;
top: 0;
left: 0;
opacity: 0;
filter: alpha(opacity=0);
background-color: #000;
pointer-events: none;
}
#menu {
display: block;
position: fixed;
z-index: 2;
top: 20px;
left: 50px;
background-color: #ff5;
width: 50px;
height: 100px;
border: 1px solid black;
pointer-events: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<span id="inside">click me please</span>
</div>
<div id="layer"></div>
<button id="button">
Add the layer again
</button>
<div id="menu">
<span>this is part of the menu</span>
</div>
The idea is to react to document events and intercept the click events in the elements that should not be responsive. That is why some callbacks return false.

Differentiate between mouse wheel scroll or scrollbar scroll?

I've searched on Stackoverflow but can't seem to find a satisfactory answer to this question. Basically I'd like to know if the scroll was done via mousewheel or the browser scrollbar.
Something like this might work for you but it is not the best solution.
If the a wheel event occurs right before the scroll event, then the scroll is done with the wheel otherwise it is done with using something else then the wheel. There is a slight time difference between both events that are triggered thats why I use a threshold currTime - lastWheelTime > 30.
$('.test').on('scroll wheel DOMMouseScroll mousewheel', function(e) {
var lastWheelTime,
currTime = (new Date()).getTime();
if( e.type === 'scroll' ) {
lastWheelTime = $(this).data().lastWheelTime || 0;
if( currTime - lastWheelTime > 30 ) {
$('.info').text('no wheel');
} else {
$('.info').text('with wheel');
}
} else {
$(this).data().lastWheelTime = (new Date()).getTime();
}
});
.test {
width: 200px;
height: 300px;
border: 1px solid red;
overflow: auto;
}
.inner {
height: 600px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="info"></div>
<div class="test">
<div class="inner"></div>
</div>
Here is my trick to detect scrolling by wheel or not
(Thanks #t.niese for the code snippet, I have made some modification for my demo)
var withWheel = true;
$('.test').on('scroll', function() {
$(".info").text("with wheel: " + withWheel);
})
$('.inner').on('mouseover', function() {
withWheel = true;
}).on('mouseleave', function(e) {
e.stopPropagation();
withWheel = false;
});
.test {
width: 200px;
height: 300px;
border: 1px solid red;
overflow: auto;
}
.info {
position: fixed;
}
.inner {
height: 600px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="info"></div>
<div class="test">
<div class="inner"></div>
</div>
I'd say both the wheel scroll and scrollbar scroll are the same. See the jquery page Here.
The scroll event is sent to an element when the user scrolls to a different place in the element. It applies to window objects
Reading this, it looks like the same event is going to be fired for both.
Another way that might be possible (i haven't tried it yet) is checking whether a mouse button was pressed during the scroll event (one has to click the scrollbar, right?).

Firefox sporadically ignoring mouse events

I am coding a JavaScript-application and have the problem, that on firefox about 5-10% of mouse clicks on a custom button are ignored.
The prototype for setting the click handler ist
function Button(parent, styleClassName) {
Element.call(this, parent, "div");
this.setStyleClass(styleClassName);
}
Button.prototype = Object.create(Element.prototype);
Button.prototype.constructor = Button;
Button.prototype.setOnClick = function(handler) {
this.domNode.handler = handler;
// sporadically skipped
this.domNode.onclick = function(event) {
console.log("ocl: " + event.currentTarget.id);
this.handler(event);
if(event.preventDefault) event.preventDefault();
event.stopPropagation();
}
this.domNode.ontouchstart = function(event) {
console.log("ots: " + event.currentTarget.id);
this.handler(event);
if(event.preventDefault) event.preventDefault();
event.stopPropagation();
}
}
An example for an affected html element (the event handler is assigned correctly to the outer div):
<div style="position: absolute; overflow: hidden; visibility: visible; left: 291px; top: 4px; width: 34px; height: 34px;" id="LandscapeScreen0_pMain_dlg_Element0_btnCloseDialog">
<svg style="position: absolute; overflow: hidden; visibility: visible; width: 32px; height: 32px; cursor: pointer;" id="LandscapeScreen0_pMain_dlg_Element0_btnCloseDialog_RefSVG0">
<use href="#svg08"/>
</svg>
</div>
Some additional information:
These handlers, once set, are never modified or removed
Not even a log message is written to the console
Using addEventListener() does not solve the problem
The problem only exists in Firefox (tested on v25.0)
most important: even the window does not retrieve the events!
regarding the last point: i added these lines for debugging:
window.onclick=function(e){console.log("onclick0");};
window.addEventListener("click",function(e){console.log("onclick1");}, true);
window.addEventListener("click",function(e){console.log("onclick2");}, false);
Whenever the button does not retrieve the mouse event, the window does not even receive one of these events either. So somehow, the events must have been dropped globally!?
So what I need is a hint what could be a possible cause for such a behaviour... it really seems to be non-deterministic when an event is skipped and when not.
Thanks for your help!

The drag doesn't work when it has a siblings img in IE

I'm trying to make a drag box with a sibling img and the 'move-obj' can be dragged.It runs correctly in other browser but IE(8,9,10). In IE, just while you hover the border can you drag the 'move-obj', but if you remove the tag 'img' it work correctly.I found that if I add a background-color to the 'move-obj',it will run correctly too, but it isn't what I want. Can somebody give me some advice?Here is the codepen
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.wrap{
position: relative;
width: 100%;
height: 100%;
background-color: #f0f0f0;
padding: 10%;
}
.wrap-inside{
position: relative;
width: 500px;
height: 500px;
border: 1px solid #ddd;
}
.move-obj{
cursor: move;
position: absolute;
width: 100px;
height: 100px;
border: 1px solid blue;
}
.bg{
width: 500px;
height: 500px;
position: absolute;
}
</style>
</head>
<body>
<div class="wrap">
<img class="bg" src="https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcTE2qkLv64zdI4z5uIbE1oSMmI0AiQcbwbhAYAyI0cF2Dwg88tb" alt="">
<div class="wrap-inside">
<div class="move-obj"></div>
</div>
</div>
</body>
</html>
If I understand you correctly if and only if you are hovering over the mov-obj div you want to be able to move around the https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcTE2qkLv64zdI4z5uIbE1oSMmI0AiQcbwbhAYAyI0cF2Dwg88tb image, right?
If this is what you want, look into either using jQuery and selecting the div on a hover event
$(.mov-obj).hover(function(event) {
//change the x and y coordinates of the image dynamically here of the image
//you can use the event.pageX and event.pageY (I think) to get how much/many pixels have been moved since the hover happened
}
or you can use pure JavaScript
document.getElementsByClassName("mov-obj").addEventListener("mouseenter", function( event ) {
//do something to change the img position dynamically
}, false);
//also do it for the mouseleave event
document.getElementsByClassName("mov-obj").addEventListener("mouseleave", function( event ) {
//do something to change the img position dynamically
}, false);
maybe set a flag letting you know that the mouseenter has happened, but not the mouseleave event
and then if and only if the mouse is inside the div add a click event to the div
while the click is pressed and the mouseleave event hasn't been triggered dynamically relocate the image depending on how much the mouse pointer has moved
(you can add a click event like this fyi)
document.getElementsByClassName("mov-obj").addEventListener("click", function( event ) {
//do something to change the img position dynamically
}, false);
or with jQuery
$(.mov-obj).click(function(event) {
//do something
}
hope this helps
Edit, just paste this code into a browser and try it out:
Note: this only works if you don't move the mouse outside of the div's width and height that you are wanting to move. I'll let you figure out how to fix that part if the mouse goes outside the div what happens
<DOCTYPE html>
<html>
<head>
</head>
<body>
<style>
#div1 {
border: 2px orange solid;
width: 500px;
height: 500px;
}
#div2 {
border: 2px purple solid;
width: 250px;
height: 250px;
position: absolute;
}
</style>
<div id="div1">
<div id="div2">
</div>
</div>
<script type="text/javascript">
// add event listeners to div
var div2 = document.getElementById("div2");
div2.addEventListener("mousedown", getOriginalPosition, false);
div2.addEventListener("mouseup", changeLocation, false);
var helperX;
var helperY;
function getOriginalPosition(event) {
//use these to help with the calculation later
helperX = event.offsetX;
helperY = event.offsetY;
}
var end_xPosition;
var end_yPosition;
function changeLocation(event) {
end_xPosition = event.pageX;
end_yPosition = event.pageY;
div2.style.left = end_xPosition - helperX;
div2.style.top = end_yPosition - helperY;
}
</script>
</body>
</html>

Categories