Why is there lag in javascript when objects follow the mouse? - javascript

For example, when using jquery ui draggable (http://jqueryui.com/draggable/) the object follows the mouse rather than moving with the mouse. It seems like there is a one frame difference in position.
Why is that? Is there a way to get around this?

The animation is chasing your mouse's movement because it has to wait for the mousemove event.
And, while the event fires fairly rapidly as you move the mouse pointer, it still fires after the pointer has actually moved. So, the box' position is always being updated to where the pointer was if it's still in motion.
The event will also be throttled by the (mostly) single-threading of JavaScript. If the engine is busy, including with a previous trigger of the event, the most recent trigger will have to wait for the engine to again become idle.
And, counting those triggers as frames, jQuery also employs easing so the box' position doesn't jump around oddly when there are irregularly-spaced frames.

You could try to build a more efficient function, but it more or less depends on the browser, and system. If your browser and/or your system is slow, then you will experience a lag while dragging or following the mouse.
I experienced no lag with the link you provided.

Related

Adobe Animate CC Canvas mouse actions on the stage are laggy and intermittent

In Adobe Animate CC HTML5 Canvas (createJS), I'm trying to do something very simple, to trigger an animated rollover when the entire stage of an ad is moused over, and to trigger an animated rollout when the mouse leaves the stage. It should be very simple, but it's not. Using mouseenter and mouseleave on the stage is laggy and only works intermittently. It's the same with mouseover and mouseout.
Here's the code that's laggy and intermittent:
stage.addEventListener("mouseenter", fl_MouseOverHandler.bind(this));
stage.addEventListener("mouseleave", fl_MouseOutHandler.bind(this));
function fl_MouseOverHandler(){
this.btnOverAnim.gotoAndPlay("on");
}
function fl_MouseOutHandler(){
this.btnOverAnim.gotoAndPlay("off");
}
I also tried mouseover and mouseout on a button the entire size of the stage and I got the same issue. (Also doing it this way doesn't work at all from inside a frame, and the ad is served inside a frame). I put the var frequency way up to 90 to see if that would help with the lagging, it didn't.
This was the simple mouseover / mouseout code I tried:
var frequency = 90;
stage.enableMouseOver(frequency);
this.bgCta.addEventListener("mouseover", fl_MouseOverHandler.bind(this));
this.bgCta.addEventListener("mouseout", fl_MouseOutHandler.bind(this));
On the createJS website it says "You can monitor whether the pointer is over the canvas by using stage.mouseInBounds AND the mouseleave / mouseenter events." So I'm wondering if using stage.mouseInBounds will help (but I can't find an example anywhere of how to use it). But I actually don't think it'll help because I think this whole problem is about createJS not reading where the mouse is fast enough.
Does anyone know how to fix this lagginess and intermittent firing? A work around? Why does createJS not just monitor the mouse events constantly like pure js?
Also mouseenter and mouseleave on the stage in createJS do not work on tablets or mobile and that's a problem, so it would be better to do this with mouseover and mouseout on a button the entire size of the stage. I did try using mouseover and mouseout and having 3 pixels space around the button between the edge of the button and the edge of the ad banner, this helped, but it was still firing intermittently.
Your thoughts and ideas please.
Mouseover checks are expensive. A canvas is essentially just a bitmap, so EaselJS can't use mouse events from the browser (you just get one for the entire canvas). Instead, mouseover in EaselJS requires every element to be drawn to a 1x1 pixel canvas, and then checked for fill. This gives pixel-perfect detection, but is costly if you are either checking a lot of content, or checking too often.
Reduce your frequency:
It looks like you have set the frequency to 90. This is really high (11 ms between checks, which is basically trying to achieve 90 fps). The default is 10, which is slower than a good framerate, but fast enough to feel responsive. You can probably bring it down to 20 or so to give you a peppy check without it being unnecessarily high.
Modify interactivity: Another thing you can do is filter out exactly what gets checked. By default, all display objects get checked -- but you can reduce this by turning off mouseEnabled on anything you don't care about (omitting them from the check), and turning off mouseChildren on containers that you want to treat as a single object (so something like a complex button is drawn once, instead of all its contents being drawn individually).
// Example
myBackground.mouseEnabled = false;
myButton.mouseChildren = false;
Hope that helps!
Friends, I know this was asked 4 years ago, but the lagginess in Adobe Animate CC V.21 is no different (horrible) regarding mouseenter, mouseleave, mouseover and mouseout. After 10 times of rolling over/out the page becomes unresponsive. I was able to alleviate this using stage.mouseInBounds on a setInterval. The original poster could not find an example, so here is what I did:
// Place this in a keyframe on the timeline where the named cta exists.
setInterval(function(scope){
if(stage.mouseInBounds){
createjs.Tween.get(scope.cta).to({scaleX:1.1, scaleY:1.1}, 150);
}
else{
createjs.Tween.get(scope.cta).to({scaleX:1, scaleY:1}, 150);
}
}, 150, this );
Make sure to pass this into the end of the setInterval function as an argument for the callback function as to not lose scope. I am guessing this can be reworked to use gotoAndPlay() on the timeline, in this instance the built-in tweener for CreateJS sufficed for what I needed.

mousemove event is triggered onscroll even when mouse was not moved on Chrome

I was trying to answer an issue with custom drop down, but challenged by an inconsistent behavior in Chrome and Firefox.
DEMO: http://jsfiddle.net/fyeht/ [Added scroll event for more clarity]
See below image, The list items can be navigated using arrow keys.
To Reproduce the issue:
Open console in Chrome (F12)
Click on an item in the list (you would notice some events getting logged in the console)
Use down arrow key to navigate to the next item in the list
Finally, the issue is noticed when you reach the last item in the view and hitting down arrow would scroll. Check the log to see 'scroll', 'mouse enter' and 'mouse move' [check the new demo]
The issue is after reaching the end of items in view, it scrolls. Even though the mouse is untouched, it triggers mouseenter and mousemove events in Chrome. In FF, on scroll it triggers just the mouseenter which make sense.
Question(s):
Why is mousemove triggered when mouse is untouched?
Is this just browser inconsistency? Could not find documentation on events triggered when scrolling? (never knew it did)
Submitted a bug report: https://code.google.com/p/chromium/issues/detail?id=241476
In your example, I see that both Chrome and FF are firing mouseenter DOM events whenever the mouse is left hovering over the <ul> and pressing the key down triggers the browser to scroll in order to bring the selected <li> into view.
However, only Chrome is additionally triggering mousemove events. One obvious difference already in the mouseenter event objects that the two throw is that for Chrome, MouseEvent.offsetX and MouseEvent.offsetY values are included, whereas in FF these properties are undefined. So, when that enter is triggered Chrome has already decided the mouse "has moved".
Since the MouseEvent.screenX and MouseEvent.screenY event context values do not change between scroll-triggered MouseEvent instances, one could perhaps distinguish between an "artificial" mouseenter / mousemove event and an "authentic" one by storing these values from prior events.
DOM Event Specification
The DOM Level 2 Event Specification for mousemove reads:
The mousemove event occurs when the pointing device is moved while it is over an element.
The Level 3 spec (working draft) is essentially the same:
A user agent must dispatch this event when a pointing device is moved while it is over an element.
Seems like it could down to whether one interprets "is moved" relatively or not.
Also, in the section of the Level 3 spec on mouse event order, it states that when a pointer is moved into an element, it triggers mouseover, mouseenter, and mousemove, in that order. Every case that is specified there always has those three together, so perhaps one might interpret it that if you are going to trigger the mouseenter event, you should also be triggering the mousemove event which corresponds to entering the element.
This is a nice demo.
In Chrome mouse movement for elements is definitely relative. In chrome I can get keydown and scroll events only if the mouse pointer is over the scroll bar. I can also get scroll only events if i use the wheel to scroll and leave the mouse over the scroll bar. It is and isn't very odd that scroll by dragging causes "mouse move", and "mouse over" events.
Not only are mouse move and mouse over events produced in profusion by the browser they are not a very good indication of a users intent. Infact these events are a useful entropy source.
To the degree that there are some minor differences this is in the context of how useful these "micro events" are individually. To work with them you must devise a way to filter them for user intention you want to link to higher level actions. Any reasonable method you choose to make sense of these events will probably detect these move - on scroll events as garbage. This is where your point is really worth noting and taking under consternation.
A first stage would be to Filter out events based on the elements and values of coordinates. It might help to create a state machine model. You might register and registered handlers in response to other events. You've identified this case where you'd want to change responsive state if or reaction criteria if a key element has a scroll bar. IF an element or it's parent has a vertical scroll bar throw out mouse moves with relatively high X values.
You might want to also ignore mouse overs if it's fired with a mouse move in that context. It starts to become impractical to handle each of these micro event one at a time even if you are changing state by registering or deregistering handlers. More information can be extracted by creating an event sequence fifo buffer.
Register event handlers to add a new event to the buffer. You might also want to collect information from timer events in this buffer to establish more context. You might create an object that keeps a fifo in an array. This would be like a queue but not in the sense of it being a place where the events are waiting to be processed. Instead your program is waiting to calculate patterns in the buffer and based on the patterns fire higher level events, accept or reject different types of events and extend, contract or save the contents of the buffer. You can then evaluate move events for changes in x and y and also create conditions given the patter of scroll mouse over and mouse move events you've demonstrated.
I really doubt there's a browser inconsistency here. You should create a mousemove event that prints out the x and y coordinate. You'll probably see that the mouse has indeed moved a little bit. If that's the case, try using the plugin hoverIntent to eliminate issues like this.
EDIT:
Using the up and down arrow keys, I'm now able to replicate the issue. Yeah, it sure looks like some kind of bug! I bet the mousemove coordinate delta is tiny. Maybe the cursor moves one or two pixels? I would say, to overcome this, add a check to the mousemove function that compares previous mousemove's x-y coordinates to the current mousemove's x-y coordinates. Determine if it's more than just a few pixels. If so, you know it's a real mousemove. If it's less, you can chalk that up as a chrome bug.
FURTHER EDIT:
It seems like you uncovered a bug where mousemove is being fired in chrome when it probably shouldn't be. There may be workarounds that you could figure out if you hack it enough. But the best solution might be just to avoid using mousemove in this situation. In general, mousemove is one of those expensive events that should be used only when you really need it.
This is not a bug. The mousemove is relative to element that the event is attached to. In your case, you see that your mouse is not moving because you took the browser window as the reference. But for that scrolling list, whenever the list is scrolled, the mouse pointing over some element of the list moved to over different element
Imagine that you as the Earth, a cup of coffee stand still on a table as the mouse, the scrollable list as the Sun : if you (window) don't move, the position of the cup of coffee (mouse) is at the same place for you; but for the Sun (list), it will see that the Earth and the cup of coffee are both moving.

creating homemade scrollbars in web pages

For years I have had a problem using javascript and jquery to control things like homemade scrollbars. I use mousedown to set a "dragging" variable to true, save the initial mouse position, and then use mousemove to track the mouse and set the div position. I condition everything on "dragging" so that once the user lets go of the mouse, or moves far enough away, then "dragging" is cleared and everything stops tracking.
The problem is, I have trouble finding out for sure when they let go of the mouse. I use mouseup and mouseout of course, but this has two problems. One is that I want the user to be able to wander outside the div being scrolled a small amount, like 10 pixels, without losing the tracking. The other problem is that sometimes I miss an event and "dragging" stays set and the mouse gets "stuck to the div" and they have problems letting go.
I need a solution. It can either be some plugin or a coding technique. I have used a number of different techniques over the years but I've never gotten a simple perfect solution.

Why can't I reliably capture a mouseout event?

I need to know when the mouse cursor leaves a div. So I hook up the mouseout event. However, if I move the mouse very quickly out of the div, the mouseout event doesn't fire. That's right: the mouse cursor was sitting still inside the div, it's now outside the div, and yet the mouseout callback hasn't been called. (It works fine if I don't move the mouse quite so fast.)
This is true in the latest Google Chrome by the way – so not just an "old browsers" problem.
A workaround:
A question about this problem has been posed before. Apparently it's just a fact of life, and the only workaround I've found is to manually monitor mousemove events, each time checking the cursor's x/y co-ordinates and seeing if they fall within the div’s bounding box, so you have more chances to "notice" if the cursor is no longer inside it.
Compared to letting the browser do all this natively, performing calculations on every pixel move is a bit of a performance hit. It's also tedious to code.
On to my question...
Why can't the browser can't reliably capture the mouseout event? If I can reliably tell when the mouse has left the div using the above workaround, why can't the browser do it?
I understand (from the answer linked above) that JavaScript doesn't try to interpolate "frames". Say if you put a mousemove handler on the document, and quickly move the mouse 200 pixels to the right in a perfect horizontal line, you might not get 200 mousemove events. A few will be missed. I don't have a problem with that.
But if some pixel movements are missed just as the mouse crosses the boundary of the div, why does it follow that the mouseout event should also be skipped? When the browser finally starts registering the mouse's position again (after a sudden fast movement), even if the mouse is now miles outside the box, the point is that it used to be in the box and no longer is. So why doesn't it then fire the mouseout event then?
I just don't get why this would be a hard problem for the browser vendors to solve. (But I trust there might be a good reason, which I'm too stupid to think of.)
I'm posting this question mainly out of curiosity, but I'm hoping the answer might give some insight that could help me work around the problem more efficiently. Also, any alternative workarounds (which are faster than the one presented above) would be welcome.
I know that you don't want a workaround, but you don't need to check mouse's x/y to know if you are in or out an element. You could simply check the element from which the mousemove event was fired. If you put a mousemove on document, the event will fire from one of its children, and you can compare that element with your element to know if it is one of its descendants.
Or you could go up the parentNode tree and stop if you find your element. Then you know you are inside the element and still in it, otherwise you reach the document and you are out.
Some browsers implement the mouseenter/mouseleave events that, I've noticed, are more accurate than mouseout. Prototype and jQuery have a workaround for browsers that don't implement these new events. Mouseleave does not fire from an element's children, whereas mouseout does.
You describe moving the mouse very quickly. When you stop, is the pointer still within the page? That is, is your mouse pointer still hovering over some part of the visible web page?
If it has gone outside, then it's not necessarily clear what the browser should do. The mouseout event should have a relatedTarget property that targets what the mouse pointer has gone into. If the mouse pointer is already outside of the page area, there would be no related target to point to.
Put another way, when the mouse leaves the page area, the browser stops tracking it and stops reporting its position. If you move your mouse fast enough, from the browser's perspective, the mouse simply disappeared. It's not until you bring the mouse back into the bounding box of the viewable page that the browser knows where it is, and then triggers all appropriate movement-based actions (like mouseout).
Why can't the browser can't reliably capture the mouseout event? If I can reliably tell when the mouse has left the div using the above workaround, why can't the browser do it?
I think you answered this one yourself when you said:
Compared to letting the browser do all this natively, performing calculations on every pixel move is a bit of a performance hit.
The browser does not interpolate between the frames, thus, as you stated it would demand a lot more resources and memory, which may be why it isn't "fixed".
If some pixel movements are missed just as the mouse crosses the boundary of the div, why does it follow that the mouseout event should also be skipped? When the browser finally starts registering the mouse's position again (after a sudden fast movement), even if the mouse is now miles outside the box, the point is that it used to be in the box and no longer is. So why doesn't it then fire the mouseout event then?
I don't know for sure, but I don't think it's a condition of "it was in and now it's out". Instead, it's whether it crosses that boundary (if MouseX - ElemOffsetX= 1). I agree, it doesn't make as much sense, but it could be because if you set the value to > 1 it would trigger the event multiple times. Otherwise it would have to keep track of the events, which is not in JS nature, seeing how it just adds events asynch to a stack.
What you could try is using jQuery's mouseleave event. This does two things, which delays the firing of the event:
It traverses the DOM tree to see if it truly left the element
I think it implements a timeout call, which should solve the interpolation problem that you noticed.
I found your question and the lack of other clear answers useful because it told me that I had to create a workaround. Which I did using the ideas presented in your question and the other contributors.
I have same problem when I use jquery mouseleave elem.bind('mouseleave', data, mouseLeavesZone);
The problem is intermittent and may be related to a busy CPU on the client. Say, for example, the CPU is busy elsewhere when your mouse moves out of a div. Then it seems logical that this could be the cause of the bug. I agree; this should be fixed by the browser vendors.
See http://jsfiddle.net/bgil2012/gWP5x/1/
(Aside: My JQuery code needs to use older jQuery methods because it has to run in Drupal 7 which is running jQuery 1.4, at this time and without applying patches that are coming).
I ran into this problem a few times and I came to accept the issue as a fact of life. But depend on your needs, you can just use CSS as I did. For example, if I just want to show/hide an element base on another element got hovered, then CSS is the way to go. Here is a working, reliable example:
.large {
width: 175px; height: 175px;
position: absolute;
border-radius: 100%;
/*hide the glass by default*/
top: -9999px;
left: -9999px;
opacity: 0;
transition: opacity .2s ease-in-out;
z-index: 100;
pointer-events: none;
}
.small:hover + .large {
opacity: 1;
}
http://codepen.io/tanduong/pen/aBMxyd
Here's a simple workaround.
In your onMouseOver listener, you can add a 'mousemove' listener to the window:
<div onMouseOver={() => {
setMouseOver(true)
let checkMouseLeave = (e: MouseEvent) => {
if (rootRef.current
&& !rootRef.current.contains(e.target as HTMLElement)) {
setMouseOver(false)
window.removeEventListener('mousemove', checkMouseLeave)
}
}
window.addEventListener('mousemove', checkMouseLeave)
}
></div>
Then you can check on each mouse move until the mouse is outside of your div (rootRef.current in our example).

jQuery smooth mouse tracking

I have my object tracking the mouse using the onmousemove event. But I would like to make it smooth. I'm sure this can't be difficult in jQuery but I'm not finding any really good resources.
One idea I had was to simply use the animate function and calculating the offsets that I want to move to. Then if the mouse moves again before the animation is complete, I would use the stop function to stop the animation. I would recalculate my destination and away I go again. This seems a little hack-ish and I also imagine that it will be a little jerky. I'm sure there has to be a better way. Any Ideas?
EDIT
Sorry I didn't make my problem very clear. I have the object tracking the mouse in real time, so that it moves exactly the same as my mouse. The problem is that I want it to be smooth and lag behind with acceleration effects like Andy Lin mentioned below. I'm just a little lost how to actually implement this.
I am afraid, that there is no better way, than animate. If you add a smoothing function, then you would simply do the same thing that animate does. Be sure, not to queue your animations or they will look weird. I got nice results with this:
var obj = $('<div style="width:50px;height:50px;background:red;position:absolute"></div>');
obj.appendTo(document.body);
$(document).bind('mousemove',function(ev){
obj.animate({top:ev.pageY,left:ev.pageX},{queue:false,duration:200,easing:'linear'})});
when you are using onmousemove, you do not need to have the object change its behavior with every invocation.
e.g. you can add a timer to say that within period of time, the object will not response to mousemove and will follow its original path, and upon timeout or mouse move stop, move according to the destination.
also, you can simulate an acceleration and slow down effect with animation settings.

Categories

Resources