I have a codepen.io example I've been working on to create a pure js collapsible side panel that is also resizable. The example works 70% of the time, but every-so-often resizing the panel will result in no "mousemove" events being emitted and the panel simply freezes (i.e. not tracking the mouse x position). I can't find the issue myself, wondering if anyone can shed some light on this one. Maybe there is a better approach to adding / removing event listeners for this kind of work that I've not thought off.
The meat of the js logic looks like the following:
const divider = document.querySelector(".divider");
const startSlide = event => {
const viewportWidth = window.visualViewport.width;
const width = viewportWidth - event.clientX;
divider.style.width = `${width}px`;
};
const stopSlide = event => {
window.removeEventListener("pointermove", startSlide, true);
window.removeEventListener("pointerup", stopSlide, true);
};
const initSlide = event => {
window.addEventListener("pointermove", startSlide, true);
window.addEventListener("pointerup", stopSlide, true);
};
divider.addEventListener("pointerdown", initSlide, true);
To reproduce the issue just attempt to slide the divider panel left and right a couple of times, eventually, it will bug!
broken codepen example
Looks like it becomes more reproducible if you quickly drag up after selecting the dividing bar. Adding a drag event listener shows that the drag on the divider is consuming the event
divider.addEventListener("drag", function( event ) {
console.log("DRAG");
}, true);
You probably need to prevent the element from consuming the drag event
#kriddy800 put me on the right track with looking at drag events. The fix for this particular issue and many related dragging type problems is to cancel the native onDragStart event, which in turn will stop future onDrag events from firing and masking the wanted onMouseMove events.
divider.ondragstart = () => false;
A great explanation of everything drag related: https://javascript.info/mouse-drag-and-drop
Related
I am trying to create simple eased scrolling. But I have hit a hurdle on mobile browsers with the selectionchange event and touchmove. I am trying to scroll the page when a user selects text and the selection moves past the current viewing area. But on mobile the touchmove event is fired after the user is finished selecting the text and removes their finger from the screen then places their finger back to the screen. I have only tested in android on chrome and opera but the effect is the same.
Is there a way to scroll my page when the user is selecting text or is this an impossibility?
Here is a sandbox Codesandbox.
And here is simple code:
const handleSelectionTouchmove = (e) => {
const pageY = e.targetTouches ? e.targetTouches[0].pageY : e.pageY;
console.log(pageY)
};
const handleSelectionTouchend = () => {
window.removeEventListener("touchmove", handleSelectionTouchmove);
window.removeEventListener("touchmove", handleSelectionTouchend);
};
const onSelectionChange = (e) => {
const selection = window.getSelection();
if (!selection.isCollapsed) {
window.addEventListener("touchmove", handleSelectionTouchmove);
window.addEventListener("touchend", handleSelectionTouchend);
}
};
Note:I have also tried using Pointer Events but the same thing happpens. I have also tried just setting a constant touchmove event and then setting a boolean isSelecting in the selectionchange event if the !selection.isCollapsed but the touch event is disabled when selecting. I have also tried just using the selectionchange event listener to just get the bounding client rect of the selection and go from there but then I run into isssues of selection direction and not accurately being able to find the selection point. In the codesandbox I have that code commented out if you wish to try it.
I have created a canvas and I have added mouse events to it:
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
canvas.width = screenWidth;
canvas.height = screenHeight;
...
// CALLED AT START:
function setup() {
// Mouse movement:
document.onmousemove = function(e) {
e.preventDefault();
target.x = e.pageX;
target.y = e.pageY;
angle = Math.atan2((target.y - localPlayer.getY()),
(target.x - localPlayer.getX()));
// Distance to mouse Check:
var dist = Math.sqrt((localPlayer.getX() - target.x)
* (localPlayer.getX() - target.x) + (localPlayer.getY() - target.y)
* (localPlayer.getY() - target.y));
var speedMult = dist / (canvas.height / 4);
socket.emit("update", {
...
});
}
document.onmousedown = function(e) {
e.preventDefault();
}
}
Now the issue is when I hold down the only left mouse button and move the mouse at the same time, my game lags a lot. Simply moving the mouse causes no lag. I have tested this on chrome and on firefox. It seems that I can only recreate the issue on chrome. Using the middle mouse button or right button has the same behaviour in the game and cause no lag. Only when using the left mouse button causes lag.
I have looked around for answers and found that I should prevent default behaviour like so:
e.preventDefault();
But that did not resolve the issue. I have also tried to update a number on the screen that represents the mouse position. And it updated normally. Only the game itself was lagging. Could it be that the onMouseMoved is never called whilst the left button is held down? But then why is it called with the middle and right button?
The issue should be with the code I a calling inside of the move method, because it works fine when I am not holding down the left key, and it works well on firefox. There must be something else going on.
EDIT: I decided to a recording on chrome to see what is going on. Here is the result:
What's really odd, when I press the middle mouse button or the right button, the game does the same thing, but it does not lag at all. What are you doing chrome?
EDIT: Test it out here: www.vertix.io note that not everyone seems to be able to reproduce this issue.
Thank you for your time.
When you hold down the left mouse button and move it in the same time, you are dragging.
Edit: In some versions of Chrome, there is a bug (when I posted this answer I had it, now I don't), which causing the drag events to be fired even without the element having the draggable attribute. Normally, drag events should only be fierd from elements which have the draggable attribute set to true (except images and anchors who are drragable by default).
According to the MDN, when drag events are being fired, mouse events, such as mousemove, are not, which means that your function isn't being called.
A possible solution are to use the same function for both drag and mousemove events:
function mouseMove(e) {
//do your things here
...
}
document.addEventListener('mousemove', mouseMove);
document.addEventListener('drag', mouseMove);
Note: If you'll use the same function for both events, you should be aware of which properties of the event you're using in the function, because of the difference between the drag and mousemove events. The two events doesn't contains the exact same properties and the behavior of some properties may not be the same in both of them.
Have you considered throttling?
Check out https://blog.toggl.com/2013/02/increasing-perceived-performance-with-_throttle/
You have the mouse event on the document. As we can not see what you have on the document it is hard to know if that is a cause of your problems.
Try moving the mouse event to the canvas only, as that is the only place you need it I presume. No point handling events for the document if its not part of the game, plus document is last on the list if child elements have events attached. They go first and then it bubbles up to yours.
As it looks like you are using a framework of some type there is in all possibility another mouse event listener that is part of the frame work that may be slowing you down by not preventing default. You will have to search the framework to see if it has a listener for any of the mouse events.
And use addEventListener rather than directly attaching the event via .onmousedown = eventHandler
eg canvas.addEventListener("mousedown",eventHandler);
And add the event listener to the element you need it for, not the document.
function mouseMove(e) {
//do your things here
...
}
document.onmousemove = mouseMove;
document.ondrag = function(e) {
mouseMove(e);
//do another things
...
}
I am working on some javascript UI, and using a lot of touch events like 'touchend' for improved response on touch devices. However, there are some logical issues which are bugging me ...
I have seen that many developers mingle 'touchend' and 'click' in the same event. In many cases it will not hurt, but essentially the function would fire twice on touch devices:
button.on('click touchend', function(event) {
// this fires twice on touch devices
});
It has been suggested that one could detect touch capability, and set the event appropriately for example:
var myEvent = ('ontouchstart' in document.documentElement) ? 'touchend' : 'click';
button.on(myEvent, function(event) {
// this fires only once regardless of device
});
The problem with the above, is that it will break on devices that support both touch and mouse. If the user is currently using mouse on a dual-input device, the 'click' will not fire because only 'touchend' is assigned to the button.
Another solution is to detect the device (e.g. "iOS") and assign an event based on that:
Click event called twice on touchend in iPad.
Of course, the solution in the link above is only for iOS (not Android or other devices), and seems more like a "hack" to solve something quite elementary.
Another solution would be to detect mouse-motion, and combine it with touch-capability to figure out if the user is on mouse or touch. Problem of course being that the user might not be moving the mouse from when you want to detect it ...
The most reliable solution I can think of, is to use a simple debounce function to simply make sure the function only triggers once within a short interval (for example 100ms):
button.on('click touchend', $.debounce(100, function(event) {
// this fires only once on all devices
}));
Am I missing something, or does anyone have any better suggestions?
Edit: I found this link after my post, which suggests a similar solution as the above:
How to bind 'touchstart' and 'click' events but not respond to both?
After a day of research, I figured the best solution is to just stick to click and use https://github.com/ftlabs/fastclick to remove the touch delay. I am not 100% sure this is as efficient as touchend, but not far from at least.
I did figure out a way to disable triggering events twice on touch by using stopPropagation and preventDefault, but this is dodgy as it could interfere with other touch gestures depending on the element where it is applied:
button.on('touchend click', function(event) {
event.stopPropagation();
event.preventDefault();
// this fires once on all devices
});
I was in fact looking for a solution to combine touchstart on some UI elements, but I can't see how that can be combined with click other than the solution above.
This question is answered but maybe needs to be updated.
According to a notice from Google, there will be no 300-350ms delay any more if we include the line below in the <head> element.
<meta name="viewport" content="width=device-width">
That's it! And there will be no difference between click and touch event anymore!
Yes disabling double-tap zoom (and hence the click delay) is usually the best option. And we finally have good advice for doing this that will soon work on all browsers.
If, for some reason, you don't want to do that. You can also use UIEvent.sourceCapabilities.firesTouchEvents to explicitly ignore the redundant click. The polyfill for this does something similar to your debouncing code.
Hello you can implement the following way.
function eventHandler(event, selector) {
event.stopPropagation(); // Stop event bubbling.
event.preventDefault(); // Prevent default behaviour
if (event.type === 'touchend') selector.off('click'); // If event type was touch turn off clicks to prevent phantom clicks.
}
// Implement
$('.class').on('touchend click', function(event) {
eventHandler(event, $(this)); // Handle the event.
// Do somethings...
});
Your debounce function will delay handling of every click for 100 ms:
button.on('click touchend', $.debounce(100, function(event) {
// this is delayed a minimum of 100 ms
}));
Instead, I created a cancelDuplicates function that fires right away, but any subsequent calls within 10 ms will be cancelled:
function cancelDuplicates(fn, threshhold, scope) {
if (typeof threshhold !== 'number') threshhold = 10;
var last = 0;
return function () {
var now = +new Date;
if (now >= last + threshhold) {
last = now;
fn.apply(scope || this, arguments);
}
};
}
Usage:
button.on('click touchend', cancelDuplicates(function(event) {
// This fires right away, and calls within 10 ms after are cancelled.
}));
For me using 'onclick' in the html element itself, worked for both touch and click.
<div onclick="cardClicked(this);">Click or Touch Me</div>
I am trying to simulate the swipe event from the iPhone with Raphaeljs.
To do so, I am using the drag and drop event.
To simulate the event in have a method in the move event that calculate the distance between my object and the mouse position. If the mouse goes after that distance I want to stop the drag and drop event. This is where I'm stuck.
Here is the code:
var start = function (event) {
},
move = function (event) {
inrange = self.inRange (circle.attr("cx"), circle.attr("cy"), event.pageX, event.pageY);
if(inrange == false){
//Stop draging!
}
},
up = function () {
circle.animate({ r: 40, "stroke-width": 1 }, 200);
};
circle.drag(move, start, up);
In the move method I need the stop the drag event or simulate a mouseup. How can I do so?
From the documentation:
To unbind events use the same method names with “un” prefix, i.e. element.unclick(f);
and
To unbind drag use the undrag method.
So I would think circle.undrag(); should work.
#Gregory You can use a JS closure to detect if the circle needs to stop dragging. And #apphacker there is a known issue in Raphael that prevents undrag from working. It is scheduled to be fixed in 2.0, but that doesn't have a release date as of yet (and the bug isn't fixed in the beta code, despite the ticket saying it is.
I recommend manually implementing the mousedown, mouseup and mousemove events using jQuery, as per #floyd's recommendation, and add a JS closure check in your move function to see if the circle needs to stop being dragged yet or not.'
It also occurs to me that your original post was last edited in Nov '10, so you might have moved on since then ;)
If you can include jQuery you can use trigger on "mouseup." If you can't include jQuery maybe just take a look at the source and lift that one function?
UPDATE
After some brief googling I came across this implementation. Only tested in in Chrome though:
function fireEvent(element,event){
if (document.createEventObject){
// dispatch for IE
var evt = document.createEventObject();
return element.fireEvent('on'+event,evt)
} else{
// dispatch for firefox + others
var evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true ); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
}
}
I'm attempting to write a Vimperator plugin to allow use of hints mode to simulate mouse over on drop down menus. I have the hints mode working and can correctly choose elements that have mouseover events attached. The problem is my function to simulate the mouse over is not working. This is what I currently have:
function SimulateMouseOver(elem)
{
var evt = elem.ownerDocument.createEvent('MouseEvents');
evt.initMouseEvent('mouseover',true,true,
elem.ownerDocument.defaultView,0,0,0,0,0,
false,false,false,false,0,null);
var canceled = !elem.dispatchEvent(evt);
if(canceled)
alert('Event Cancelled');
}
The above code works for some pages but not for others. For example it doesn't work on AccuWeather. Any ideas how to simulate a mouse over that will work for most pages?
here's some code to start with to create the event, simpler and works for more browsers (if you don't need to specify exact mouse coordinates)
if( document.createEvent ) {
var evObj = document.createEvent('MouseEvents');
evObj.initEvent( 'mouseover', true, false );
elem.dispatchEvent(evObj);
} else if( document.createEventObject ) {
elem.fireEvent('onmouseover');
}
hope that helps
In case anyone bumps into this looking for a framework agnostic way to fire any HTML and Mouse event (and set some options, if needed), have a look here: How to simulate a mouse click using JavaScript?
You may only trigger mouseover event on fields/elements that have a mouseover event bound to them. You can't just hijack the mouse.