I need to detect when an user move out the mouse outside the view-port (example mouse is on browser address bar) even when the mouse button is being held.
As you can see from the code below, I am able to detect it using mouseout and mouseleave but when keeping the mouse button hold and moving out of the view-port these events are not fired.
Any idea how to solve this issue?
I target FF and Chrome latest version.
http://jsbin.com/gesehoneri/edit?html,output
document.addEventListener('mouseout', function () {
console.log('mouseout');
})
document.addEventListener('mouseleave', function () {
console.log('mouseleave');
})
Try this:
document.addEventListener('mousemove', function(e) {
var top = e.pageY;
var right = document.body.clientWidth - e.pageX;
var bottom = document.body.clientHeight - e.pageY;
var left = e.pageX;
if (top < 10 || right < 10 || bottom < 10 || left < 10) {
console.log('Mouse is out the viewport!');
}
});
body,
html {
height: 100%;
}
With this code, if you press the button inside the window, hold it and move the mouse outside the window, it logs the text. Does this help you?
function myFunctioName(e){
if(e.pageY < 0 || e.pageY > window.innerHeight) {
console.log("outside window vertical");
};
if(e.pageX < 0 || e.pageX > window.innerWidth) {
console.log("outside window horizontal");
};
}
window.addEventListener("mousemove", myFunctioName);
window.addEventListener("mousedown", myFunctioName);
Updated for use without JQuery and included both directions.
I have a page full of draggable divs only in horizontal (on axis X).
When I'm in a touch device I can't scroll down the page, due conflicts between scroll and drag.
Here's a jsfiddle example (test in touch devices and try to scroll).
My draggable code:
$(".cell").draggable({
axis: "x",
drag: function (event, ui) {
var size = $(".cell").width();
if (ui.offset.left > size - em(4)) {
ui.position.left = size - em(4);
}
},
start: function (event, ui) {
if (initialPosition == null) {
initialPosition = ui.position.left;
}
$(".cell").not(ui.helper).each(function () {
var pos = $(this).position().left;
if (pos > 0) {
$(this).animate({
left: initialPosition
}, 200, null);
}
});
},
stop: function (event, ui) {
var size = $(".cell").width();
if (ui.position.left > initialPosition) {
if (ui.position.left - initialPosition >= size / 3) {
ui.helper.animate({
left: size - em(4)
}, 200, null);
} else {
ui.helper.animate({
left: initialPosition
}, 200, null);
}
}
}
});
I want to detect if the user is scrolling vertically before start dragging and cancel the
horizontal dragging.
Help me, please. How can I make this work?
I had a problem similar to this one and eventually found a fairly simple solution.
In my scenario I had an inbox list whose items that you could drag to the left or right to expose action buttons. The entire inbox item must be draggable -- so the use of a drag handle was not an option.
jQuery's draggable prevents vertical scrolling on touch screens if the touch was initiated inside a draggable element. So if the screen was filled with draggable inbox items, then the user would become trapped -- unable to scroll up or down.
The solution that worked for me was to measure any change in the cursor's vertical position and use window.scrollBy to manually scroll the window by the same amount:
var firstY = null;
var lastY = null;
var currentY = null;
var vertScroll = false;
var initAdjustment = 0;
// record the initial position of the cursor on start of the touch
jqDraggableItem.on("touchstart", function(event) {
lastY = currentY = firstY = event.originalEvent.touches[0].pageY;
});
// fires whenever the cursor moves
jqDraggableItem.on("touchmove", function(event) {
currentY = event.originalEvent.touches[0].pageY;
var adjustment = lastY-currentY;
// Mimic native vertical scrolling where scrolling only starts after the
// cursor has moved up or down from its original position by ~30 pixels.
if (vertScroll == false && Math.abs(currentY-firstY) > 30) {
vertScroll = true;
initAdjustment = currentY-firstY;
}
// only apply the adjustment if the user has met the threshold for vertical scrolling
if (vertScroll == true) {
window.scrollBy(0,adjustment + initAdjustment);
lastY = currentY + adjustment;
}
});
// when the user lifts their finger, they will again need to meet the
// threshold before vertical scrolling starts.
jqDraggableItem.on("touchend", function(event) {
vertScroll = false;
});
This will closely mimic native scrolling on a touch device.
I use jquery.ui.touch-punch.js and this worked for me, line 38:
event.preventDefault();
Reference
Trying to create a scrolling div. Wanted to stop (thescrollingdiv) div once it has reached a particular top position and scrolled all the way to the bottom and not overshoot the parent div into infinity scrolling zone. thescrollingdiv does not have any height specified but its parent div does.Thanks.
$('#div a).click(function(e){
e.preventDefault();
$('#thescrollingdiv').stop(true,true).animate({ "top": '-=100px'}, 500)
ScrollTop tells you where you are at. Check the existing top against scrolltop and work the math to set your limits.
var scrollTop = $('#thescrollingdiv').scrollTop();
var newTop = parseFloat($('#thescrollingdiv').css('top')) - 100;
if (scrollTop < 0) {
newTop = 0;
}
$('#thescrollingdiv').stop(true,true).animate({ "top": newTop}, 500)
UPDATE
Something like this.
var topLimit = 0;
var bottomLimit = 800;
var containerTop = parseFloat($('container').css('top'));
var containerBottom = parseFloat($('container').css('height')) + containerTop;
var destination = containerTop - 100;
// compensate for going too far up
destination = (destination < 0) ? 0 : destination;
// compensate for going too far up
destination = (containerBottom > bottomLimit) ? bottomLimit : destination;
// now that you know you are within your custom limits, animate it.
animate(destination);
This is almost pseudo code as I don't know what your code looks like, but it gives you an idea. You have to actually DO THE WORK in setting the limits for your 'newTop', before you call animate in the first place.
You can figure it out. Don't be a lazy programmer, though.
You can easily see the problem on the first page here: http://m.vancouverislandlife.com/
Scroll down (slide up) and allow the content to leave the page, and it doesn't bounce back and is lost forever. However, on pages whose content does overflow the page and is therefore supposed to be scrollable, the scrolling works correctly (see Accomodations > b&b's and scroll down for an example of this).
I noticed that on my computer, the scrolling on the first page is always stuck at -899px. I can't find anybody else who's experienced this problem and no matter what I try, I just can't fix it! Help!
(It's not exactly urgent, however, as the target audience of iPhones and iPod Touches aren't affected by this since they have so little screen room.)
Okay, new problem. To solve the iScroll issue, I just created a custom script. However, it's not working correctly on the actual device. On desktop browsers, it works just fine. On mobile, it occasionally jumps back to the top and won't recognize some touches. This is probably because of the way I cancelled the default event and had to resort to a bit of a hack. How can I fix this? (Yup - simple problem for a +500 bounty. Not bad, huh?)
Here's the script, and the website is at the usual place:
function Scroller(content) {
function range(variable, min, max) {
if(variable < min) return min > max ? max : min;
if(variable > max) return max;
return variable;
}
function getFirstElementChild(element) {
element = element.firstChild;
while(element && element.nodeType !== 1) {
element = element.nextSibling;
}
return element;
}
var isScrolling = false;
var mouseY = 0;
var cScroll = 0;
var momentum = 0;
if("createTouch" in document) {
content.addEventListener('touchstart', function(evt) {
isScrolling = true;
mouseY = evt.pageY;
evt.preventDefault();
}, false);
content.addEventListener('touchmove', function(evt) {
if(isScrolling) {
evt = evt.touches[0];
var dY = evt.pageY - mouseY;
mouseY = evt.pageY;
cScroll += dY;
momentum = range(momentum + dY * Scroller.ACCELERATION, -Scroller.MAX_MOMENTUM, Scroller.MAX_MOMENTUM);
var firstElementChild = getFirstElementChild(content);
content.style.WebkitTransform = 'translateY(' + range(cScroll, -(firstElementChild.scrollHeight - content.offsetHeight), 0).toString() + 'px)';
}
}, false);
window.addEventListener('touchend', function(evt) {
isScrolling = false;
}, false);
} else {
content.addEventListener('mousedown', function(evt) {
isScrolling = true;
mouseY = evt.pageY;
}, false);
content.addEventListener('mousemove', function(evt) {
if(isScrolling) {
var dY = evt.pageY - mouseY;
mouseY = evt.pageY;
cScroll += dY;
momentum = range(momentum + dY * Scroller.ACCELERATION, -Scroller.MAX_MOMENTUM, Scroller.MAX_MOMENTUM);
var firstElementChild = getFirstElementChild(content);
content.style.WebkitTransform = 'translateY(' + range(cScroll, -(firstElementChild.scrollHeight - content.offsetHeight), 0).toString() + 'px)';
}
}, false);
window.addEventListener('mouseup', function(evt) {
isScrolling = false;
}, false);
}
function scrollToTop() {
cScroll = 0;
content.style.WebkitTransform = '';
}
function performAnimations() {
if(!isScrolling) {
var firstElementChild = getFirstElementChild(content);
cScroll = range(cScroll + momentum, -(firstElementChild.scrollHeight - content.offsetHeight), 0);
content.style.WebkitTransform = 'translateY(' + range(cScroll, -(firstElementChild.scrollHeight - content.offsetHeight), 0).toString() + 'px)';
momentum *= Scroller.FRICTION;
}
}
return {
scrollToTop: scrollToTop,
animationId: setInterval(performAnimations, 33)
}
}
Scroller.MAX_MOMENTUM = 100;
Scroller.ACCELERATION = 1;
Scroller.FRICTION = 0.8;
I think Andrew was on the right track with regards to setting the height of the #wrapper div. As he pointed out that,
that.maxScrollY = that.wrapperH - that.scrollerH;
Normally, this would work. But now that you've changed your #content to position: fixed, the wrapper element is no longer "wrapping" your content, thus that.wrapperH has a value of 0, things break.
Disclaimer: I did not go through the entire script so I may be wrong here
When manually setting a height to #wrapper, say 500px, it becomes,
that.maxScrollY = 500 - that.scrollerH;
The folly here is that when there's a lot of content and the window is small, that.scrollerH is relatively close in value to 500, say 700px. The difference of the two would be 200px, so you can only scroll 200 pixels, thus giving the appearance that it is frozen. This boils down to how you set that maxScrollY value.
Solution (for Chrome browser at least):
Since #wrapper effectively contains no content, we cannot use it in the calculations. Now we are left with the only thing that we can reliably get these dimensions from, #content. In this particular case, it appears that using the content element's scrollHeight yield what we want. This is most likely the one that has the expected behavior,
that.maxScrollY = that.scrollerH - that.scroller.scrollHeight;
scrollerH is the offsetHeight, which is roughly the height of what you see in the window. scroller.scrollHeight is the height that's considered scrollable. When the content does not exceed the length of the page, they are roughly equivalent to one another. That means no scroll. When there are a lot of content, the difference of these two values is the amount of scroll you need.
There is still a minor bug, and this looks like it's already there. When you have a lot of content, the last few elements are covered up by the bar when scrolled to the bottom. To fix this, you can set an offset such as,
that.maxScrollY = that.scrollerH - that.scroller.scrollHeight - 75;
The number 75 arbitrary. It's probably best if it's the height of the bar itself with 2 or 3 pixels for a bit of padding. Good luck!
Edit:
I forgot to mention last night, but here are the two sample pages that I used in trying to debug this problem.
Long page
Short page
This may be a CSS issue. In your stylesheet (mobile.css line 22), try removing position:fixed from #content.
That should allow the document to scroll normally (vertical scrollbar on a computer, "slideable" on a mobile browser).
Elements with position:fixed exit the normal flow of the document, their positioning is relative to the browser window. This is probably why you're having issues with scrolling. Fixed positioning is generally for elements which should always remain in the same place, even when the page is scrolled (ie. a notification bar "pinned" at the top of a page).
No definite solution, but more a direction I'd go for:
#wrapper and #content's overflow:hidden paired #content's postion:fixed and seem to be the cause of the issue.
If position: fixed is removed from #content, scrolling is possible but the "blank" divs are wrongly layered (tested in Firefox 5).
Your wrapper div seems to have a height of 0. So all the calculations are negative, setting it's height to the window height will correct the scroll issue. When I manually set the wrappers height via firebug and chromes debug bar the scroll functions as it should.
You #content div seems to have its size change on resize, probably a better idea to have the #wrapper div have its size change and then have #content inherit the size.
[Edit]
You don't believe me so codez, From iscroll-lite.js
refresh: function () {
var that = this,
offset;
that.wrapperW = that.wrapper.clientWidth;
that.wrapperH = that.wrapper.clientHeight;
that.scrollerW = that.scroller.offsetWidth;
that.scrollerH = that.scroller.offsetHeight;
that.maxScrollX = that.wrapperW - that.scrollerW;
that.maxScrollY = that.wrapperH - that.scrollerH;
In your page that translates to,
that.wrapperH = 0;
that.maxScrollY = -that.scrollerH
When a scroll finishes, this code gets called.
var that = this,
resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x,
resetY = that.y >= 0 || that.maxScrollY > 0 ? 0 : that.y < that.maxScrollY ? that.maxScrollY : that.y;
...
that.scrollTo(resetX, resetY, time || 0);
See that that.maxScrollY > 0 ? ? If maxScrollY is negative then scrolling up will never bounce back.
I ended up just making my own, small script to handle the scrolling:
// A custom scroller
function range(variable, min, max) {
if(variable < min) return min > max ? max : min;
if(variable > max) return max;
return variable;
}
var isScrolling = false;
var mouseY = 0;
var cScroll = 0;
if("createTouch" in document) {
// TODO: Add for mobile browsers
} else {
content.addEventListener('mousedown', function(evt) {
isScrolling = true;
mouseY = evt.pageY;
}, false);
content.addEventListener('mousemove', function(evt) {
if(isScrolling) {
var dY = evt.pageY - mouseY;
mouseY = evt.pageY;
cScroll += dY;
var firstElementChild = content.getElementsByTagName("*")[0];
content.style.WebkitTransform = 'translateY(' + range(cScroll, -(firstElementChild.scrollHeight - content.offsetHeight), 0).toString() + 'px)';
}
}, false);
window.addEventListener('mouseup', function(evt) {
isScrolling = false;
}, false);
}
and modifying a few other parts. It does save a lot of download time, I suppose, also.
I'm still going to accept answers and award the bounty in 5 days, though.
Changed question warrants a new answer. I took a look at the code and I saw that you calculated the momentum on each step of the "move" function. This does not make sense because the momentum is used after the move has ended. What this meant was to capture the mouse position at the beginning, and then calculate the difference at the end. So I added two new variables,
var startTime;
var startY;
Inside the start event (mousedown/touchstart), I added,
startY = evt.pageY;
startTime = evt.timeStamp || Date.now();
Then I have the following for my end handler,
var duration = (evt.timeStamp || Date.now()) - startTime;
if (duration < 300) {
var dY = evt.pageY - startY;
momentum = range(momentum + dY * Scroller.ACCELERATION, -Scroller.MAX_MOMENTUM, Scroller.MAX_MOMENTUM);
} else {
momentum = 0;
}
I also removed the momentum calculation from inside of mousemove/touchmove. Doing this removed the jumping around behavior that I was seeing on my iPhone. I am seeing other unwanted behaviors as well (the whole window "scrolls"), but I'm guessing that you've been working to get rid of those so I didn't attempt.
Good luck. Here's a coded up page that I duplicated for my testing. I also took the liberty to refactor the code for this section to remove some duplicated code. It's under mobile3.js if you want to look at it.
This Jquery problem has been bugging me for a while now. I developed a script, with one function detecting when the mouse leaves via the top of the page. Here is the code:
$(document).bind("mouseleave", function(e)
{
console.log(e.pageY);
if (e.pageY <= 1)
{
now = new Date();
for (i=0; i < times.length; i++)
{
if (now.getTime() > times[i][0] && now.getTime() < times[i][1])
{
$.fn.colorbox({iframe:true, width:650, height:600, href: "work.html", open: true});
}
}
}
});
This works perfectly for me in all browsers. For some reason it works randomly in Chrome and seemingly not at all in Firefox for a friend that tested the site. In my browser (firefox 3.5.3), e.pageY is logged in the console box as a number near 0, however in my friends browser (also firefox 3.5.3) the lowest value is around 240. I have no idea why this is happening considering identical browsers. Does anyone have a clue as to how to debug this, or another more reliable method to detect when the mouse goes out of the webpage via the top? I hope this makes sense.
The problem appears if your window scrolls down, add a bunch of <br/>s to your page and scroll down one line and you'll see it.
So instead of looking to see if e.pageY <=1, subtract out the scrollTop:
if (e.pageY - $(window).scrollTop() <= 1)
{
// do something
}
I used another technic, almost works for all browsers. The trick is using $("body") or $(window).
$(window) do not work for IE, but $("body") works partially for FF as the body might not fill the whole window.
Here's the full page code:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script><script>
var mouseX = 0;
var mouseY = 0;
var theFrame;
$(function() {
theFrame = $("body"); //$(window) for non-IE
theFrame.mousemove( function(e) {
//horizontal distance from edge
mouseX = Math.min(theFrame.width() - e.pageX, e.pageX);
//vertical distance from top
mouseY = e.pageY;
$("#mx").html(mouseX);
$("#my").html(mouseY);
});
theFrame.mouseout(function() {
if(mouseY<=mouseX)
$("#in_out").html("out-top");
else
$("#in_out").html("out");
});
theFrame.mouseover(function() {
$("#in_out").html("in");
});
});
</script>
</head>
<body>
<span id="in_out"></span>
<br />Hor: <span id="mx"></span>
<br />Ver: <span id="my"></span>
</body>
</html>
$(document).on('mouseleave', leaveFromTop);
function leaveFromTop(e){
if( e.clientY < 0 ) // less than 60px is close enough to the top
alert('y u leave from the top?');
}
This doesn't work well on older IE version, because those versions don't report the mouse position as should, but it's good enough.
Here is a vanilla JS solution if you just want something light weight that doesn't need to work in EI
/**
* Trigger an event when the cursor leaves the top of the window
* #param {*} threshold how close does it need to be to the top
* #param {*} cb callback function to trigger
*/
function onExit (threshold, cb) {
threshold = threshold || 60
var hasExited = false
document.addEventListener('mouseout', function (e) {
if (e.clientY < threshold && e.movementY < 0 && !hasExited) {
hasExited = true
cb(e)
}
})
}
Example Usage:
onExit(20, function() {
console.log('Mouse has left the top of the window!')
}
In order to detect mouseleave without taking in account the scroll bar and the autcomplete field or inspect :
document.addEventListener("mouseleave", function(event){
if(event.clientY <= 0 || event.clientX <= 0 || (event.clientX >= window.innerWidth || event.clientY >= window.innerHeight))
{
console.log("I'm out");
}
});
Conditions explanations:
event.clientY <= 0 is when the mouse leave from the top
event.clientX <= 0 is when the mouse leave from the left
event.clientX >= window.innerWidth is when the mouse leave from the right
event.clientY >= window.innerHeight is when the mouse leave from the bottom
Just keep
event.clientY <= 0
If you only want to detect exit on top