I've run into an issue using Raphael for SVG effects on an IE browser. When I mouseover an object, the animation occurs as expected. On mouseout, however, the mouseout action is never called, so the object(s) are stuck in their mouseover state.
I've seen others complain about this issue in the past, but the only solution I saw was to force the mouseover event on every object to return everything != current object to their normal state. I'd rather not do a general "reset everything" because I have quite a few objects, so I'm wondering if anyone has an alternative they can suggest. I was thinking about storing the last object with the last triggered mouseover in a variable and only resetting that on every mouseover, which could work....
Since Raphael 2.0.2, there's been an issue in Raphael and Internet Explorer (all versions) where various manipulations of a path such as resetting a transform, .toFront(), or .toBack() called from hover() while hovering can cause hover ins to loop endlessly and/or hover outs to be missed.
While hover mostly works fine in IE for mouseout, there are a few things I've found that that can trip it up, causing it to a) ignore mouseouts as you describe and b) trigger the mouseover event recursively such that if you put a console.log in there, IE developer tools' console breaks and turns grey... which sometimes seems to also stop it recognising mouseouts.
Here are the things I've encountered which cause this:
Resetting a transform, which would cause the element to move from under the mouse, then reapplying it putting the element back under the cursor. non-IE carries on like nothing happened and works fine, IE freaks out as described above.
.toFront() and .toBack() - non-IE recognises the moved element as being the same element in the same X Y position, IE freaks out as described above.
There are some observations and demonstrations in this jsfiddle (read & uncomment the various comments).
A good workaround here, is to have some kind of flag like for example 'path.data( 'hoverIn', true );on mouse in and 'path.data( 'hoverIn', false ); on mouse out, then wrap any .toFront() or offending transforms in a check that !path.data( 'hoverIn' ) so that it can only happen once until the mouse out happens. Or, store a reference to the most recently hovered path somewhere after the toFront() or whatever, then don't toFront() or whatever if this path is also the most recently hovered one.
I had the same problem (map with regions that changed background on hover) and the deal-braker in IE9/Opera for me was the toFront() method. I removed that and it works fine.
I gone around this limitation by putting code inside anonymous function and then calling it via setTimeout inside event handler.
If you add a rect as a background underneath (and containing) the object you are trying to mouseout of, you can effectively get a mouseout effect by adding another hover event handler to the background rect.
Related
I am implementing a functionality where I can add, drag, and delete "sticky notes" on a webpage. The app is built in Vue js but it also renders content in an iframe. All the notes that are added to the page have to be on top of the iframe (which is almost 80% of the main .vue page), therefore positioning here is important and I also have to preserve the positions because I must render the notes on the same position on next page reload. The problem is that there's a lot of lag in dragging the "note" element.
The "sticky note" itself is a very lite weight separate component.
I have noticed that this because of the iframe that's present on the vue page because when I inspect the DOM in the browser and delete the iframe and then try dragging the "note" component then it works smoothly.
Things I have tried:
Use throttling: I tried to use loadsh.throttle but that does not make any difference.
Inject "sticky note" code inside the iframe: I tried to inject a sticky note element into the iframe and append it to iframe's body. It actually makes the dragging very smooth. But I don't want to go ahead with this solution because then I will have to write a lot of extra code to maintain the state of multiple notes (which can be done easily with Vue js).
In this solution, instead of using the document of the main page to attach events for dragging, I attached all the events to iframe.contentDocument.
So the question here is how do I make the drag smooth while using vue.js
Sandbox Link: https://codesandbox.io/s/affectionate-jang-3c1hw?file=/src/components/HelloWorld.vue
In this gif, the background is actually the iframe, I have reduced its opacity to hide it.
I could not actually include the iframe in sandbox code because of cross-origin problems but I have included a lot of extra content in that sandbox to make it heavy.
UPDATE:
Using the chrome task manager, I found that the page is only taking max 200MB memory and the GPU process is taking another 200MB. I am running this on a system with 16GB RAM. So I don't think it's a memory issue. But there is a sudden spike in the CPU consumption when I start dragging the element (up to 40%).
UPDATE:
I have found the fix for this problem. The actual problem wasn't lag but it was mouse trailing i.e. the draggable element wasn't able to catch up with the fast moving mouse cursor. And the slowness was due to e.preventDefault in the dragMouseDown method inside Note.vue. Just removing the e.preventDefault fixed all the problems. Also just adding a return false at the end of dragMouseDown method seems to cause the same amount of lag.
function dragMouseDown(e) {
e = e || window.event;
// e.preventDefault(); --> this line causes the mouse trailing issue
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
// return false; --> adding this line also causes mouse trailing problem.
}
So for now I have just removed the preventDefault from this function. But I tried searching and could not find any explanation of this behavior. Also I am not sure if not cancelling the event can cause any other issue.
The problem is that you're working with mousemove without using requestAnimationFrame to debounce. Here's a working example using debounce (CodeSandbox link)
Generally, anytime you're trying to animate with javascript, you want to use requestAnimationFrame. Another thing you could try is using transform to change the position of the element instead of absolute positions.
Here's the MDN reference on requestAnimationFrame.
Here's an article by Paul Irish about using transform instead of absolute positioning to speed up a drag/drop.
As your extra description in the comments,
It starts to lag when these a lot of other content on screen. There's
a v-for in the sandbox. make it do more iterations and it will start
to laggy
The problem you met is the Dom elements were too many on your page then caused high memory usage. (Actually I tried <div v-for="i in 10000" :key="i">", then it took around 3GBs of memory), finally, everything works slowly and laggy
If your page has tons of Dom elements, you may have to consider dynamically add only visible items into the Dom tree when scrolling.
Even there are some packages that already implements this feature.
Below is one demo which uses RecycleScroller of vue-virtual-scroller:
100K items in the Codepen
You will see even the number of the items are 100,000, it still works smoothly.
PS: you may notice the below statement in the user guide of the above package=vue-virtual-scroller
The browsers have a size limitation on DOM elements, it means that
currently the virtual scroller can't display more than ~500k items
depending on the browser.
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.
Okay, so, I have a bit of a strange situation which I have encountered multiple times. I'm looking for a simple/the best solution, NOT the overcomplicated solution I used before (and have since forgotten).
You see, I have a slideshow. I also have a couple of buttons that float on top of the slide show (visible here: http://marsxplr.com/view-13378)
Now, I have these buttons appear when the user's mouse goes over the slideshow. However, the minute the user's mouse goes over the buttons, mouseleave is called (even though the mouse is still technically over the slideshow!). This causes the buttons to disappear whenever the mouse goes over them, which is obviously not something I would like to happen.
I solved this by then making each button re-show its self whenever it receives a mouseover.
But now, we have a problem...
The button is on the very edge of the slideshow. That means that when mouseleave is called, one of two things happened:
A:The mouse went off of the button but not off of the slideshow, and mouseEnter got called on the slideshow meaning that no action should be taken because slideshow will eventually take care of hiding the button in its mouseleave.
B: The mouse simply left the slideshow and button altogether meaning that we should HIDE the button!
So my question is, how do I tell the difference between these two possibilities? Yes, yes, one solution would be to just always make the button hide its self -- and rely on the slideshow re-showing the button... But I am worried that the slideshow onmouseenter will be called BEFORE the button onmouseleave on certain browsers! This could cause issues as you can probably tell. In fact, thinking about this, I am already susceptible to this. For instance, if the mouse goes from the slideshow to the button, and the onmouseleave for my slideshow is called AFTER the onmousleave causing the button to still dissapear...
So, long story short, I need to know one of these two things:
Is there a guaranteed order in which these events are called?
Or is there a way to tell where the mouse actually WENT when mouseleave is called?
As you can see in my example above, I am using mootools, so a mootools solution is fine. It would, however, be interesting to see a vanilla-js solution as well.
EDIT:
My buttons are floated and on a separate z-index, so the standard operation for a parent-child DOM relationship does not occur
No such problem with standard DOM events.
As for the order, it bubbles from the deepest elements up to the document root.
There's also an optional (see the false param in the code below) capture phase, when the order is reversed. From the root to each inner element under the cursor (for mouse events).
To check on which inner element of the original element the event actually occurred, you can inspect the target property of the event object:
el.addEventListener('mouseout', function (e) {
var target = e.target;
if (target.classList.contains('whatever')) {
// Do something
}
}, false);
As per Georges Oates Larsen's comment, there's also relatedTarget, designed specifically for events involving several elements.
For a 'shooting gallery'-like game, there are images with an onMouseOver event that will show a crosshair (which follows the mouse) and an onMouseOut event that will hide it.
However, the instant the crosshair becomes visible, it covers the below image, activating its onMouseOut event which hides the crosshair.
This creates a 'flicker' effect which is very, very ugly.
Is there a way to prevent the crosshair image from obscuring the target below?
One easy solution is to create an onMouseOver for the crosshair that shows it (I know this is redundant, but it works for menus and the like).
Another solution would be to set a timeout in the onMouseOut to hide the crosshair. It will reduce the flickering and the crosshair will be visible out of the target for a short time.
Edit:
And another would be to perform a collision detection between the mouse and the elements, changing the mouseOver to a mouseMove in the target's parent element:
http://jsfiddle.net/sHecT/1/
The code is quite long, but its logic is what I've said above.
Just a note: The getElementsByClassName doesn't work in older browsers, but you can use an array of targets, as you create them, add them to an aray (probably you already has one) and use it in place of this call;
This one illustrates an easing function to position the crosshair smothly and the cursor is hidden when over the target
http://jsfiddle.net/sHecT/2/
Instead of using an image for your background, and an image for your cross-hairs, what if you used a div with a background image for your background instead, and then made the cross-hairs image a child of that div?
I haven't tested this, but since the cross-hair image would now be part of the content of the background div, I would imagine that it would no longer trigger a mouseout.
I believe The pointer-events property is exactly what you're looking for. It basically prevents an element from being the target of any sort of mouse-event so the elements underneath it can handle them instead. Parent elements can still react to the event.
HOWEVER it is only supported in the later versions of Firefox, Safari, and Chrome, and it probably won't be in the CSS specification until CSS4. It an awesome solution for supported browsers, but if you want to fix the issue in any version of IE or Opera you'll also need an alternate solution. I felt like it deserved a mention here though. :D
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).