Using jQ the .toggle() behaviour seems to depend on the location of the client mouse pointer at the time of page load events. And that, in terms of reliable (Turin like) behaviour, is totally useless!!
For example hovering over an iframe video while the page loads inverts the toggle state. Each subsequent hover event has the reverse effect, hiding instead of
$("iframe").hover(function () {
$('#sliderContainer').fadeToggle();
});
Initially I searched to find some mitigation. Am I correct to conclude that at some timepoint there will always be a risk of hover-toggle becoming inverted?
Also I've read in SO that .toggle has been deprecated, but don't see any indication of that on jQ's website. What's going on there?
I still found .toggle to be non-deterministic with mouseenter and mouseleave.
Those mouse events do work with display:none and display: block, with the provision that when the mouse is over the 'toggle' area for the time during page load, then the mouse has to leave the area and trigger the mouseenter event.
At least that is expected and dependable behaviour! Then animation has to be added.
Related
I'm trying for numerous days to solve the following issue.
I have a menu located on the top of the page which needed to be open using swipedown event (I'm using Hammer.js jQuery version).
Problem is, every time I try to interact using swipes I either scroll the page (swipeup) or pulling the page down same as described in the following question.
Here is what I've tried so far:
overflow: hidden; on the body element with an inner container with overflow: auto, swipe on top element still triggered document scroll.
Setting preventDefault on the document also disabled lower elements events in the DOM hierarchy and by that I had no swipe events working in the page.
Also tried using stopPropagation on the actual element when the event occurs, to prevent the bubbling up the chain for the event, the result cause the object to not respond to the events (swipes) and document scroll worked with no problems.
Any ideas how can I still keep page scroll but also when using common gestures, such as swipedown/swipeup, on specific elements that the element only will be affected?
Here is an example using JSFiddle, to better demonstrate the issue.
Would appreciate ideas/thoughts
I don't know if this will help, but I've always liked to use drag more than swipe. Using Hammer on my projects, swipes were a bit finicky. And from a UX standpoint, drag feels instantaneous vs a swipe. Much like, mousedown vs mouseup/click. So in instances where it's appropriate, and I believe in the case of showing swipey menu it is, I'd opt for drag.
Replacing your example with drag rather than swipe, and also using CSS transition, -webkit-transition, rather than jQuery's animate (drag will trigger like a mousemove, vs a click or a mouseup) seemed to make it work.
Hammer('.nav').on('dragdown', function(e){
e.gesture.preventDefault()
$(".blue").html("down")
$('.nav').css({"top":"0px"});
})
.on('dragup', function(e){
e.gesture.preventDefault()
$(".blue").html("dragup")
$('.nav').css({"top":"-150px"});
});
//Added in CSS, for .nav
.nav {-webkit-transition:0.5s top;}
Example
This does still have the page overscroll. A preventDefault() on document.ontouchstart would could fix that but that breaks scrolling. You might be able to do a selective preventDefault() by checking the scrollOffset perhaps. But I guess in the long run, I'd recommend something like iScroll.
Example
Also maybe tweak the hitbox for the drag to be a bit larger. Which I did in the last example. I attached the dragdown event on the document instead of the "menu" so the menu doesn't have to be visibly bigger.
Hammer(document).on("dragdown",function(e){
//calculate ratio of first touch from top
var pos=e.gesture.startEvent.center.pageY/window.innerHeight
if(pos<0.2){ //drag occurs in the first 20% of the screen
menu.style.marginTop="0px" //or animate here
e.gesture.preventDefault()
e.gesture.stopPropagation();
}
})
You should use the preventDefault function of the orginal gesture, to stop the browsers default behaviour, see here: https://github.com/EightMedia/hammer.js/wiki/Event-delegation-and-how-to-stopPropagation---preventDefaults
When you have a div element, on which you want to register swipe events, you would do the following:
$('#swipeDiv').hammer().on("swipe", function(ev) { ev.gesture.preventDefault(); });
That should prevent the scrolling of the page, but only if the swipe happens on the div element.
I noticed mouseenter event triggered when mouse is untouched but the page below the cursor is scrolled.
Check out this fiddle: http://jsfiddle.net/F3EwW/
Steps to reproduce:
Click on a li
Use up/down arrow keys to scroll the items
You would notice the mouseenter event getting triggered when the li below is scrolled to the view.
Note: To notice this behavior, make sure the mouse cursor is above li and leave it untouched.
Initially, I accepted this as a default behavior and went on with a work around to handle this in my code.. but then I got curious and wanted to verify this behavior in any documentation which I couldn't find it anywhere.
Does anyone know if this behavior is documented anywhere in spec or any authentic webpage?
I looked up w3spec event scroll and mouse event order, but couldn't locate anything about this.
Also the spec description for mouseenter is as follows,
A user agent must dispatch this event when a pointing device is moved onto the boundaries of an element or one of its descendent elements. This event type is similar to mouseover, but differs in that it does not bubble, and must not be dispatched when the pointer device moves from an element onto the boundaries of one of its descendent elements.
In Chrome, you would notice mouseover to be triggered as well. I have posted a question and a bug report already on this.
You realize that you have $('li').mouseenter(function () { ?
this caused the mouseenter event to be binded to each and every one of this li elements so when you are using the up and down key to scroll and your mouse is still inside the ul it keeps entering a new li. This was not an unintended feature the mouse in entering the new element.
The behaviour you are looking for is more something like this:
$("element").bind("mousemove mouseenter", function(event) {
//...
});
Also you need to realize the DOM understands the movement of the mouse relative to the document not where your mouse is on your screen as your OS does.
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.
I have several pages that are all very similar. They have some javascript rollover links (images are preloaded, then there is a onMouseOver event that calls an image swap function and finally, there is a onMouseOut event that restores the original image).
When the user clicks on a rollover link that points to another page that has a rollover link on the exact same position, the image on the new page would be expected to load on the "over" state. This is not the case in Chrome and Safari (IE and Firefox work as expected).
So... On page load, is there a way to check if the mouse is already hovering the image to swap it right away? Something like "OnMouseAlreadyOver"?
Thank you.
If you using jQuery, it works without any problems!
http://jsfiddle.net/beuae
(not only for buttons, for divs also)
Actually, jQuery is a very good framework which assures everything goes as you expect, and cross-browser. This example confirms it.
The W3C standard says
onmouseover = script [CT]
The onmouseover event occurs when the pointing device is moved onto an element. This attribute may be used with most elements.
onmousemove = script [CT]
The onmousemove event occurs when the pointing device is moved while it is over an element. This attribute may be used with most elements.
mouseover is fired on moving over the boundary of the object. mousemove happens when the mouse is already over the element.
You may need to use onmousemove (or even both).
You may need to actually do the calculation based on the element position and the mouse cursor position.
//Get Mouse Position
document.onmousemove=getMouseCoordinates;
function getMouseCoordinates(event){
ev = event || window.event;
mouseX = ev.pageX;
mouseY = ev.pageY;
}
You can't without passing a variable to the other page or using cookies to track which was hovered (and that will fail over if people do change their mouse position)
In theory you could check the mouse position and the button position however there is no way to get the mouse position unless an event is triggered, so the mouse has to move and if it move the CSS :hover should get triggered.
It's a minor issue tho, I doubt most people are going to click a link, wait for the next page and then expect that link to be hovered and ready to click again (why wouldn't anyone one to keep clicking the same button unless it does different things)
From a UX point of view I wonder if webkit doesn't have the best approach here, why port the action of one page to another.
You can use document.getElementFromPoint(mouseX, mouseY) to get the element, but the only way to get the cursor's position is via an event. The problem is, the only events are clicks and mouse movements, which require user input from the beginning, which is what you're trying to avoid.
In short, no, it's not possible to do with JavaScript. You're left with using CSS.
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).