Reliable "mouseenter" without jQuery - javascript

I've been looking everywhere and I can't seem to find a reliable mouseenter event.
The closest I found was: mouseenter without JQuery
function contains(container, maybe) {
return container.contains ? container.contains(maybe) : !!(container.compareDocumentPosition(maybe) & 16);
}
var _addEvent = window.addEventListener ? function (elem, type, method) {
elem.addEventListener(type, method, false);
} : function (elem, type, method) {
elem.attachEvent('on' + type, method);
};
var _removeEvent = window.removeEventListener ? function (elem, type, method) {
elem.removeEventListener(type, method, false);
} : function (elem, type, method) {
elem.detachEvent('on' + type, method);
};
function _mouseEnterLeave(elem, type, method) {
var mouseEnter = type === 'mouseenter',
ie = mouseEnter ? 'fromElement' : 'toElement',
method2 = function (e) {
e = e || window.event;
var related = e.relatedTarget || e[ie];
if ((elem === e.target || contains(elem, e.target)) &&
!contains(elem, related)) {
method();
}
};
type = mouseEnter ? 'mouseover' : 'mouseout';
_addEvent(elem, type, method2);
return method2;
}
The only issue is that when i run it:
_mouseEnterLeave(ele, 'mouseenter', function(){
console.log('test');
});
I get 40-47ish (different every time) executions at once each time the listener fires.
I tried the Quirksmode one too: http://www.quirksmode.org/js/events_mouse.html#mouseenter
function doSomething(e) {
if (!e) var e = window.event;
var tg = (window.event) ? e.srcElement : e.target;
if (tg.nodeName != 'DIV') return;
var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
while (reltg != tg && reltg.nodeName != 'BODY')
reltg= reltg.parentNode
if (reltg== tg) return;
// Mouseout took place when mouse actually left layer
// Handle event
}
However this one was extremely unreliable and not only that, it assumed the parent/element was a DIV. This has to be more dynamic. This is for a library/script so I can't include jQuery.
In short, I have an element that is hidden until the mouse moves. Once it moves it appears for as long as the mouse is moving OR if the mouse is hovering over the element itself. Less code would be awesome simply because only WebKit doesn't support mouseenter natively and it feels like a waste to have that huge chunk of code from the first example just to support Chrome for a small UI thing.

Is it possible to just scrap the mouseenter and instead use mousemove instead? That takes care of showing it when the mouse is moving. To make it stay visible when hovered directly on the element, just use CSS instead.
#your_element {
display: none;
}
#your_element:hover {
display: block;
}

Related

Vimeo iFrame Stealing Mouse Wheel Event on Firefox

I made this example here: http://jsbin.com/pokahec/edit?html,output
// creates a global "addWheelListener" method
// example: addWheelListener( elem, function( e ) { console.log( e.deltaY ); e.preventDefault(); } );
(function(window,document) {
var prefix = "", _addEventListener, onwheel, support;
// detect event model
if ( window.addEventListener ) {
_addEventListener = "addEventListener";
} else {
_addEventListener = "attachEvent";
prefix = "on";
}
// detect available wheel event
support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
"DOMMouseScroll"; // let's assume that remaining browsers are older Firefox
window.addWheelListener = function( elem, callback, useCapture ) {
_addWheelListener( elem, support, callback, useCapture );
// handle MozMousePixelScroll in older Firefox
if( support == "DOMMouseScroll" ) {
_addWheelListener( elem, "MozMousePixelScroll", callback, useCapture );
}
};
function _addWheelListener( elem, eventName, callback, useCapture ) {
elem[ _addEventListener ]( prefix + eventName, support == "wheel" ? callback : function( originalEvent ) {
!originalEvent && ( originalEvent = window.event );
// create a normalized event object
var event = {
// keep a ref to the original event object
originalEvent: originalEvent,
target: originalEvent.target || originalEvent.srcElement,
type: "wheel",
deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
deltaX: 0,
deltaZ: 0,
preventDefault: function() {
originalEvent.preventDefault ?
originalEvent.preventDefault() :
originalEvent.returnValue = false;
}
};
// calculate deltaY (and deltaX) according to the event
if ( support == "mousewheel" ) {
event.deltaY = - 1/40 * originalEvent.wheelDelta;
// Webkit also support wheelDeltaX
originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX );
} else {
event.deltaY = originalEvent.detail;
}
// it's time to fire the callback
return callback( event );
}, useCapture || false );
}
})(window,document);
You can test in Firefox that scroll event is fired, except when over vimeo iframe ( and I guess any iFrame )
Is there any solution to fire event on iframe ?
PS - I want to use this in a custom scrollbar
This is basically by design. Your code should be completely unaware of what the user does inside an IFRAME (especially one from a different origin like YouTube - this is a part of the web's security architecture, as mandated by the Same Origin Policy.)
Now, even in the cross-origin case browsers can choose to let scrolling affect the frame's ancestor if the frame itself doesn't scroll. This scrolling should happen without any events firing on the top document - see Chrome's behaviour if you scroll to the bottom of this IFRAME and keep scrolling:
http://jsfiddle.net/8cj0dofx/1/
HTML:
<iframe src="data:text/html,<body style='background:grey;height:550px'>Hello" seamless></iframe>
<div style="height:100px">Hello</div>
JS:
document.addEventListener('DOMMouseScroll', function(e){
document.getElementsByTagName('div')[0].firstChild.data += ' ' + e.type
});
document.addEventListener('mousewheel', function(e){
document.getElementsByTagName('div')[0].firstChild.data += ' ' + e.type
});
What you'll see is that when you have scrolled to the end of the IFRAME, the main document will scroll but no events will fire until the mouse is above the parent document.
It looks like it's a bug in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1084121
So there may not be a straightforward way to handle this. But since the action has an effect even if it's not being dispatched, there's a workaround that can be used. It may not work in every situation, but it should cover many cases.
Instead of detecting wheel event, you detect scroll, and use a switch detecting if the mouse is clicked or not. If the window scrolls and the mouse isn't clicked, then it's most likely from the mousewheel. Other cases will be if you trigger it from a script, in which case this can be handled easily also.
One case you won't handle is when the window cannot scroll anymore, then you won't get the event.
It would look like this:
var mouseDown = false;
function handle_wheel() {
if (!mouseDown) {
document.getElementById("debug-textarea").value = document.getElementById("debug-textarea").value + ' wheel';
} else {
document.getElementById("debug-textarea").value = document.getElementById("debug-textarea").value + ' scroll';
}
}
window.onscroll = handle_wheel;
window.onmousedown = function () {
mouseDown = true;
}
window.onmouseup = function () {
mouseDown = false;
}
http://jsfiddle.net/wu9y6yua/4/
I was facing the same problem but with a zoom on scroll feature.
Since it's not possible to capture the mousewheel event without hacks, I think the best option is to place a transparent div over the iframe and add the &autoplay=1 parameter
in the vimeo/youtube url on click.

modify hoverIntent to handle touch events on mobiles

Good day all.
I'm having some problems with hoverintent.js a jquery plugin that handle the mouseOver events in a different way than normal.
Due to some complications, I can't modifiy anything but the js of this plugin, but I need to make it compliant with touch events and not only with mouseOver and mouseLeave.
after some debugs, I have managed to recognize this part of the code to be the one to modify:
var handleHover = function(e) {
// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
if ( p == this ) { return false; }
// copy objects to be passed into t (required for event object to be passed in IE)
var ev = jQuery.extend({},e);
var ob = this;
// cancel hoverIntent timer if it exists
if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
// else e.type == "onmouseover"
if (e.type == "mouseover") {
// set "previous" X and Y position based on initial entry point
pX = ev.pageX; pY = ev.pageY;
// update "current" X and Y position based on mousemove
$(ob).bind("mousemove",track);
// start polling interval (self-calling timeout) to compare mouse coordinates over time
if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
// else e.type == "onmouseout"
} else {
// unbind expensive mousemove event
$(ob).unbind("mousemove",track);
// if hoverIntent state is true, then call the mouseOut function after the specified delay
if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
}
}
};
// bind the function to the two event listeners
return this.mouseover(handleHover).mouseout(handleHover);
what I've done so far is to make the function working different with mobiles:
var handleHover = function(e) {
isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if(isMobile){
console.log("Ismobile");
}else{
... Same code as before here ...
}
// bind the function to the two event listeners
return this.mouseover(handleHover).mouseout(handleHover);
and now i'm struck. I would like it to "change" its behavior to handle the touch, and not the mouse over event, so on mobiles I will need to touch the element, instead to hovering on it. May someone give me an help? Am I on the right way? Is it the right way to think of it?
unluckily I have only the possibility to change this file and some few more.
Recently i bumped into several problems with changing hoverIntent.js, and ended up in writing my own plugin: hoverDelay.js (much simpler, and less code). see if you can use it, and modify it to your own needs (and maybe contribute the mobile code to it :-)

Chrome (maybe Safari?) fires "blur" twice on input fields when browser loses focus

Here is an interesting jsfiddle.
In Firefox:
Run the fiddle
Click in text input
Click somewhere else. Should say "1 blurs".
Click in the text input again.
ALT-TAB to another window. Fiddle should now say "2 blurs".
In Chrome, at step 5, it says "3 blurs". Two separate "blur" events are fired when the whole browser loses focus. This is of interest because it means that it's not safe to assume, in a "blur" handler, that the element actually had focus just before the event was dispatched; that is, that the loss of focus — the transition from "being in focus" to "not being in focus" — is the reason for the event. When two "blur" events are generated, that condition is not satisfied during the handling of the second event, as the element is already not in focus.
So is this just a bug? Is there a way to tell that a "blur" event is bogus?
The reason it is firing twice is because of window.onblur. The window blurring triggers a blur event on all elements in that window as part of the way javascript's capturing/bubbling process. All you need to do is test the event target for being the window.
var blurCount = 0;
var isTargetWindow = false;
$(window).blur(function(e){
console.log(e.target);
isTargetWindow = true;
});
$(window).focus(function(){
isTargetWindow = false;
});
$('input').blur(function(e) {
if(!isTargetWindow){
$('div').text(++blurCount + ' blurs');
}
console.log(e.target);
});
​
http://jsfiddle.net/pDYsM/4/
This is confirmed Chrome bug. See the Chromium Issue Tracker
The workaround is in the accepted answer.
Skip 2nd blur:
var secondBlur = false;
this.onblur = function(){
if(secondBlur)return;
secondBlur = true;
//do whatever
}
this.onfocus = function(){
secondBlur = false;
//do whatever
}
This probably isn't what you want to hear, but the only way to do it seems to be to manually track whether the element is focused or not. For example (fiddle here):
var blurCount = 0;
document.getElementsByTagName('input')[0].onblur = function(e) {
if (!e) e = window.event;
console.log('blur', e);
if (!(e.target || e.srcElement)['data-focused']) return;
(e.target || e.srcElement)['data-focused'] = false;
document.getElementsByTagName('div')[0].innerHTML = (++blurCount + ' blurs');
};
document.getElementsByTagName('input')[0].onfocus = function(e) {
if (!e) e = window.event;
console.log('focus', e);
(e.target || e.srcElement)['data-focused'] = true;
};
Interestingly, I couldn't get this to work in jQuery (fiddle here) ... I really don't use jQuery much, maybe I'm doing something wrong here?
var blurCount = 0;
$('input').blur(function(e) {
console.log('blur', e);
if (!(e.target || e.srcElement)['data-focused']) return;
(e.target || e.srcElement)['data-focused'] = false;
$('div').innerHTML = (++blurCount + ' blurs');
});
$('input').focus(function(e) {
console.log('focus', e);
(e.target || e.srcElement)['data-focused'] = true;
});
You could also try comparing the event's target with document.activeElement. This example will ignore the alt+tab blur events, and the blur events resulting from clicking on Chrome's... chrome. This could be useful depending on the situation. If the user alt+tabs back into Chrome, it's as if the box never lost focus (fiddle).
var blurCount = 0;
document.getElementsByTagName('input')[0].onblur = function(e) {
if (!e) e = window.event;
console.log('blur', e, document.activeElement, (e.target || e.srcElement));
if ((e.target || e.srcElement) == document.activeElement) return;
document.getElementsByTagName('div')[0].innerHTML = (++blurCount + ' blurs');
};​
​
I'm on Chrome Version 30.0.1599.101 m on Windows 7 and this issue appears to have been fixed.
I am experiencing the same and the above posts make sense as to why. In my case I just wanted to know if at least one blur event had occurred. As a result I found that just returning from my blur function solved my issue and prevented the subsequent event from firing.
function handleEditGroup(id) {
var groupLabelObject = $('#' + id);
var originalText = groupLabelObject.text();
groupLabelObject.attr('contenteditable', true)
.focus().blur(function () {
$(this).removeAttr('contenteditable');
$(this).text($(this).text().substr(0, 60));
if ($(this).text() != originalText) {
alert("Change Found");
return; //<--- Added this Return.
}
});
}
Looks like an oddity of angularjs gives a simpler solution when using ng-blur; the $event object is only defined if you pass it in:
ng-blur="onBlur($event)"
so (if you aren't using ng-blur on the window) you can check for:
$scope.onBlur = function( $event ) {
if (event != undefined) {
//this is the blur on the element
}
}

Javascript event handler on body but not on input

I have the following event handler
document.addEventListener('keydown', handleBodyKeyDown, false);
HOW DO i prevent it from occurring when inside a input box
Within your handleBodyKeyDown function, check if
event.target.tagName.toUpperCase() == 'INPUT'
(or 'TEXTAREA').
Note: For older versions of IE, use event.srcElement.tagName.
Like so:
document.addEventListener('keydown', handleBodyKeyDown, false);
function handleBodyKeyDown(event)
{
var e = event || window.event,
target = e.target || e.srcElement;
if (target.tagName.toUpperCase() == 'INPUT') return;
// Now continue with your function
}
P.S. Why are you using addEventListener if you have jQuery on the page? In jQuery, all of this gets sorted out for you:
$(document).on('keydown', ':not(input)', function(e)
{
// Your code goes here...
});
In your handleBodyKeyDown method, check to see if the event originated on an input element:
function handleBodyKeyDown(event) {
if (event.target.tagName.toUpperCase() === 'INPUT') {
return; // do nothing
}
// do the rest of your code
}
Note that the toUpperCase call is necessary because the conditions that determine the case of the tagName property are quite complicated and sometimes all but uncontrollable.
See event.target at MDN.
If you are using jQuery you can try this which uses is() method to test the target element is input then do nothing.
function handleBodyKeyDown(event) {
if ($(event.target).is("input")) {
return;
}
else{
//Do your stuff here
}
}
This worked for me:
const fromInput = event => event.srcElement instanceof HTMLInputElement;
function handleBodyKeyDown(event) {
if(fromInput(event))
return;
// do your magic here
}
You could do something like:
handleBodyKeyDown = function(e) {
var e = e || window.event
if (e.target.tagName != "INPUT") {
// handle this since it isn't input
}
}
Sometimes (as to me) it is better not to prevent it to occur, but to ignore in the event cases, when it occured in the input. It's looks like this is also your case as well.
Just inspect evt.target || evt.srcElement property (modern frameworks do this normalization work for you, so, most probably this will be called target) whether it's input or not. If not, just ignore.
QuirksMode tells you how to get an event's target. You can check that it is not an input:
function doSomething(e) {
var targ;
if (!e) var e = window.event;
if (e.target) targ = e.target;
else if (e.srcElement) targ = e.srcElement;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode;
if( targ.tagName != "INPUT" ) {
//Perform your action here
}
}
Your question is tagged jQuery, in which case you can just test event.target as the framework normalizes this for you.
$(document).bind("keydown", function (event) {
if(event.target.tagName != "INPUT") {
//Do something
}
});
HandleBodyKeyDown function will be invoked in any case. You can not prevent its call on the method of recording as you indicated. You can only add a logic for checking if this an 'input' and return. Additionaly (if needed) you can prevent it from bubble up:
function handleBodyKeyDown(ev) {
ev=ev||event;
var sender=ev.target||ev.srcElement;
if(sender.tagName.toLowerCase()==="input") {
if(ev.stopPropagation)ev.stopPropagation();
else ev.cancelBubble=true; // for IE8 or less
return true; // do not prevent event from default action
}
// your code for global keydown
}
If you're using Prototype (which you have tagged but you also have two other frameworks tagged) then the event can be registered and filtered in one like this:
document.on('keydown', ':not(input)', handleBodyKeyDown);

Custom "mouseenter" function doesn't fire

Here's the JSFiddle.
I'm trying to make mouseenter work on Chrome, Firefox, etc. using the following function:
var addMouseenter = (function () {
var contains = function (parent, elem) {
return parent.contains ? parent.contains(elem) :
!!(parent.compareDocumentPosition(elem) & 16);
},
wrap = function (elem, method) {
return function (e) {
if (elem === e.target && !contains(elem, e.relatedTarget)) {
method.call(elem, e);
}
};
};
return function (elem, listener) {
var listener2 = wrap(elem, listener);
elem.addEventListener('mouseover', listener2, false);
};
}());
Everything worked fine until I ran into this specific situation:
Element A has one of these custom mouseenter listeners
Element A contains Element B
Element B is right up against the edge of Element A
You enter Element A at that same edge
My expectation was that the mouseover event would be triggered on Element B and bubble up to Element A. However, that does not appear to be the case. I tested with Chrome 13 and Firefox 3.6 and got the same result. Did I mess something up?
If you don't oppose using jQuery:
$(document).ready(function() {
$('#first').mouseover(function (e) {
if ($(e.target).attr('id') != 'second') {
alert('hello');
}
});
});
Tried that in your JSFiddle and it works:
when you enter the green square it doesn't fire; when you enter red square from outside it fires; when you enter red square from green square it fires. That's what you wanted right?
new JSFiddle
Or keeping your javascript approach:
// Misc set-up stuff
var greet = function () { alert('Hi, my name is "' + this.id + '."'); },
first = document.getElementById('first'),
second = document.getElementById('second');
// The Actual Function
var addMouseenter = (function () {
var contains = function (parent, elem) {
return parent.contains ? parent.contains(elem) :
!!(parent.compareDocumentPosition(elem) & 16);
},
wrap = function (elem, method) {
return function (e) {
//if (elem === e.target && !contains(elem, e.relatedTarget)) {
if (elem === e.target && (e.target != second)) {
method.call(elem, e);
}
};
};
return function (elem, listener) {
var listener2 = wrap(elem, listener);
elem.addEventListener('mouseover', listener2, false);
};
}());
// GOGOGO
addMouseenter(first, greet);
http://jsfiddle.net/AUc88/
The reason my custom function wasn't firing is because it didn't work.
I updated the fiddle showing that all is as it should be.
My mistake was only checking to see if e.target was the same as the element I had attached the listener to. What I needed to be checking was if they were the same or if e.target was a child of the element.
When you mouse over the two squares really quickly, it only registers the mouseover event on the inner one, and because my listener was attached to the outer one, the elem === e.target test was failing.
So I changed the if code in the wrap function to this:
if ((elem === e.target || contains(elem, e.target)) &&
!contains(elem, e.relatedTarget)) {
e.stopPropagation();
method.call(elem, e);
}

Categories

Resources