Mouse capture in non-IE browser? - javascript

I have made something like a drag-and-drop element with JS.
function Draggable(elm) {
this.d = elm;
this.style.position = "absolute";
elm.onselectstart = elm.ondragstart = function() { return false; }
elm.addEventListener('mousedown', this._start.bindAsEventListener(this), false);
}
Draggable.prototype._start = function (event) {
this.deltaX = event.clientX;
this.deltaY = event.clientY;
if (!this.dm) {
this.dm = document.createElement("div");
this.dm.setAttribute("class", "dragger");
this.dm.onmousemove = this._move.bindAsEventListener(this);
this.dm.onmouseup = this._stop.bindAsEventListener(this);
this.dm.onselectstart = RetFalse;
this.dm.ondragstart = RetFalse;
}
document.body.appendChild(this.dm);
this.lastX = this.lastY = 0;
this.ondragstart();
return false;
}
Draggable.prototype._move = function (event) {
var newx = (event.clientX - this.deltaX);
var newy = (event.clientY - this.deltaY);
if (newx < this.x0) newx = this.x0;
if (newx > this.x1) newx = this.x1;
if (newy < this.y0) newy = this.y0;
if (newy > this.y1) newy = this.y1;
this.d.style.left = newx + "px";
this.d.style.top = newy + "px";
if (window.getSelection) window.getSelection().removeAllRanges(); else document.selection.empty();
return false;
}
Draggable.prototype._stop = function (event) {
document.body.removeChild(this.dm);
return false;
}
The "dragger" is transparent DIV that fills the whole page, to prevent the dragged target from losing capture when mouse moves too fast. (If I could capture the mouse, I would need it.)
.dragger {
cursor:move;
position:absolute;
width:100%;height:100%;
left:0px;top:0px;
margin:0px;padding:0px;
z-index:32767;
background: transparent;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
However, if I:
Press left mouse button on the draggable element
Drag it outside the client area (outside the brower window)
Release mouse button
The element will lose the capture, so that if I move the cursor back,
without having receive a mouse-up event, the element follows the cursor everywhere.
(until you click to make a mouse-up again.)
Just now, I saw it perfectly done on this website: (www.box.net)
Even if you release mouse button outside the browser window, the blue selecting box can still resize when the cursor moves, and disappear when button is released.
But I cannot receive any mousemove or mouseup when cursor is outside.
What API can I use to capture the mouse?
As you can see, I'm using Chrome Browser.
It is said that there's no API like HTMLElement.setCapture in non-IE browser.
This page uses jQuery, but what does jQuery use?
What is the raw javascript Code to do that?

Instead of creating a big, transparent element (dm), bind your mouse events to window.
It gets mouse events everywhere on the page; during dragging you'll keep getting mousemove events even if the cursor goes outside the window, as well as a mouseup if you release the mouse button outside the window.
P.S. If you call .preventDefault() on the mousedown event, the browser won’t select any text and you won’t have to clear the selection on mousemove.

Although it is a little outdated (FF now supports setCapture), I found this article to be extraordinarily helpful. The basis of the fix goes something like this:
var dragTarget = element.setCapture ? element : document; // setCapture fix
I've set up this little example. The javascript is copied straight from a webpage I'm building for a client where it works perfect*. Unfortunately I haven't been able to find a fix for draggable content inside an iframe, so it will still appear broken in Chrome if viewed at jsFiddle, Codepen, etc. You'll have to trust me that it works (or try it out yourself). If anyone knows of a fix for this iframe issue, please share.
*in Chrome, Safari and FF, I haven't tested in Opera or IE yet

Related

Css :hover although element not in front

im writing a website on which pictures, ordered in a grid, are shown. I want to make it possible to drag them around with the mouse and zoom in and out with the mouse wheel. This already works so far. here is what my code looks like:
var clicked = [0,0];
var pos = [0,0]; // <-- This ist the position of the image(s)
var dragging = false
var zoom = 1;
//this function is for zooming in and out
window.onmousewheel = function(event)
{
if (event.deltaY > 0)
{
zoom *= 0.9;
}
else
{
zoom *= 1.1;
}
update(0, 0);
}
window.onmousedown = function(event)
{
clicked = [event.clientX, event.clientY];
dragging = true;
}
window.onmousemove = function(event)
{
if (dragging == false){return;}
update((event.clientX-clicked[0]),(event.clientY-clicked[1]))
}
window.onmouseup = function(event)
{
pos = [pos[0] + (event.clientX-clicked[0]), pos[1] + (event.clientY-clicked[1])];
dragging = false;
}
function update(addX, addY) //<-- this function just updades the position of the images by using the zoom and pos variable and the addX and addY parameter
All of this works very fine. But it has one Bug: When i'm start draging while the mouse is directly over one of the images, then when i release the mouse the mouseup event is not triggered an so everything is still moving until you click again with your mouse. What i also do not like is that if you are dragging while the mouse is over one of the images, it shows this standard chrome browser image moving thing.
My Idea for solving this problems was, making a div with opacity: 0; in the front over everything, which fits the whole screen. looks like this:
(css)
#controller
{
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
height:100%;
width:100%;
z-index: 999;
opacity: 0;
}
(html)
<div id="controller"></div>
And now it works fine. I also can drag when i start with the mouse direct over an image. But then i realized that now i can not make any click event or an simple css :hover over one of the images anymore, obviously because the invisible div is now in the front :(
Has anyone of you an idea how two solve this problem?
Put the onmouseup inside onmousedown:
window.onmousedown = function(event)
{
clicked = [event.clientX, event.clientY];
dragging = true;
window.onmouseup = function(event)
{
pos = [pos[0] + (event.clientX-clicked[0]), pos[1] + (event.clientY-clicked[1])];
dragging = false;
}
}

draggable interaction broken on Firefox for Android after page scroll

I'm working on a javascript draggable interaction that has to work with both mouse and touch input and does not have any dependencies. So far it works fine on desktops and mobiles.
Except Firefox for Android shows the following behaviour:
page is not scrolled: fine
page is scrolled vertically: element can only be dragged horizontally
page is scrolled horizontally: element can only be dragged vertically
page is scrolled both vertically and horizontally: element can't be
dragged at all
scroll page back to the very top and left: element can be dragged as
expected again
The code:
var evtStart, evtMove, evtEnd;
if ('ontouchend' in window) {
evtStart = 'touchstart';
evtMove = 'touchmove';
evtEnd = 'touchend';
} else {
evtStart = 'mousedown';
evtMove = 'mousemove';
evtEnd = 'mouseup';
}
// BASIC DRAGGABLE INTERACTION
// No further configuration, just drags ....
var panel = document.querySelector('.testpanel');
panel.addEventListener(evtStart, function(e) {
e.preventDefault();
var styles = window.getComputedStyle(panel),
left = parseFloat(styles.left), // css left on mousedown
top = parseFloat(styles.top), // css top on mousedown
psx = e.pageX || e.touches[0].pageX, // pointer x on mousedown
psy = e.pageY || e.touches[0].pageY; // pointer y on mousedown
// function actually draging the elmt
var drag = function (e) {
e.preventDefault();
var pmx = e.pageX || e.touches[0].pageX, // current pointer x while pointer moves
pmy = e.pageY || e.touches[0].pageY; // current pointer y while pointer moves
panel.style.left = left + (pmx - psx) + 'px'; // set new css left of elmt
panel.style.top = top + (pmy - psy) + 'px'; // set new css top of elmt
};
panel.addEventListener(evtMove, drag);
panel.addEventListener(evtEnd, function () {
panel.removeEventListener(evtMove, drag);
});
});
Demo page
Again, it works fine on desktop and mobiles except FF for Android.
Why does it not work on FF for Android? Is it something in my code or is it a bug in FF? So far I could not find anything helpful.
I would greatly appreciate any help.
Finally I found it myself:
Just swap pageX/pageY with clientX/clientY and it works in FF for Android as well.
I guess the implementation in FF for Android is somewhat different, pageX/pageY are not finalized standards yet according to MDN.

Detecting hover or mouseover on smartphone browser

I have an alphabetical scrolling bar (ASB) in my app, which most smartphones have in their Contacts app.
Now, I have no problem to scroll to specific item when my finger touchstart touchend click etc.. on the ASB.
But, I have problem on capturing hover or mouseover event on my smartphone.
I have tried touchstart touchswipe touchend mouseenter mousemove or hover with no lucks.
Here's the Fiddle or Codepen to play around on your mobile.
Any suggestion is appreciated.
TL;DR; touchmove, touchstart and touchend are the events that made this possible.
I've found that people keep telling me that it's not possible on non-native app to provide the functionality of hover event on smartphone.
But, the modern smartphone browsers have actually provided the functionalities. I realized that the solution is literally lying in a very simple place. And with few tweaks, I've figured how I can simulate this behavior to cross-platform even though it's a bit cheating.
So, Most oftouchevents are passing the arguments that have the needed information where the user touches on the screen.
E.g
var touch = event.originalEvent.changedTouches[0];
var clientY = touch.clientY;
var screenY = touch.screenY;
And since I know the height of every button on my ASB, I can just calculate where the user hovers the element on.
Here's the CodePen to try it easier on mobile touch devices. (Please note this only works on touch devices, you can still use chrome on toggled device mode)
And this is my final code,
var $startElem, startY;
function updateInfo(char) {
$('#info').html('Hover is now on "' + char + '"');
}
$(function() {
var strArr = "#abcdefghijklmnopqrstuvwxyz".split('');
for (var i = 0; i < strArr.length; i++) {
var $btn = $('<a />').attr({
'href': '#',
'class': 'btn btn-xs'
})
.text(strArr[i].toUpperCase())
.on('touchstart', function(ev) {
$startElem = $(this);
var touch = ev.originalEvent.changedTouches[0];
startY = touch.clientY;
updateInfo($(this).text());
})
.on('touchend', function(ev) {
$startElem = null;
startY = null;
})
.on('touchmove', function(ev) {
var touch = ev.originalEvent.changedTouches[0];
var clientY = touch.clientY;
if ($startElem && startY) {
var totalVerticalOffset = clientY - startY;
var indexOffset = Math.floor(totalVerticalOffset / 22); // '22' is each button's height.
if (indexOffset > 0) {
$currentBtn = $startElem.nextAll().slice(indexOffset - 1, indexOffset);
if ($currentBtn.text()) {
updateInfo($currentBtn.text());
}
} else {
$currentBtn = $startElem.prevAll().slice(indexOffset - 1, indexOffset);
if ($currentBtn.text()) {
updateInfo($currentBtn.text());
}
}
}
});
$('#asb').append($btn);
}
});
#info {
border: 1px solid #adadad;
position: fixed;
padding: 20px;
top: 20px;
right: 20px;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="info">
No hover detected
</div>
<div id="asb" class="btn-group-vertical">
</div>
The hover event is triggered with click/touch events on mobile phone, because you can not simply hover an element on a touch screen.
You can demonstrate this behaviour by simply using the css hover and/or focus selectors to modify content, you can see, that before clicking elements remain the same, but after clicking they retain modified styles.
bind touchstart on a parent. Something like this will work:
$('body').bind('touchstart', function() {});
You don't need to do anything in the function, leave it empty. This will be enough to get hovers on touch, so a touch behaves more like :hover and less like :active.
Similar question How do I simulate a hover with a touch in touch enabled browsers?
The code you provided in your Fiddle or Codepen is working fine. So what's the fuss?
Well, in mostly air or touch-triggering gadgets like smartphones and tablets, you cannot simply use the hover thing function because you can't hover thing on it to make some events. You can only use the hover function when you are using for example a detachable keyboard for tablet (or something that uses mouse or finger scroller).

without jquery i need to find out if the mouse is over an element, not determine when it becomes over (in case it doesn't move to trigger onmouseover)

without jquery
basically what I am looking for is the ability to see if the mouse is over a div when a countdown finishes
if the user is over the div then perform action for that div
onmouseover only triggers when the mouse crosses the threshold of the div, if the mouse hasn't moved it wouldn't trigger, so that wouldn't work
I need to determine if the mouse is currently over a div at a specific point in time, if it has moved or not from the starting point
all of my hunting has only found onmousover, and nothing to see if the mouse just happens to be there to begin with
I don't have the javascript skills to determine overall coords of div, then map mouse coords and see if it fits there... which is what I believe I need to do
After reading the second answer (the one with millions of a elements) on this SO question, I've came up with this method works without moving the mouse on page load, without involving millions of elements.
HTML
<div id=t></div>
CSS
#t {
/* for illustrative purposes */
width: 10em;
height: 5em;
background-color: #0af;
}
#t:hover {
border-top-style: hidden;
}
JavaScript
document.addEventListener('click', function () {
var c = window.getComputedStyle(document.getElementById('t')).getPropertyValue('border-top-style');
if (c === 'hidden') {
alert('Mouse in box');
} else {
alert('Mouse not in box');
}
}, false);
As stated earlier, bind to the finish event of your countdown instead of the click event on the document.
You may also use any CSS style that's changed on :hover, I chose border-top-style as it is conspicuous. If you're using a border, choose something else.
Here's a jsFiddle.
set a flag to true onmouseover and to false onmouseleave. when countdown finishes if flag is true then it is over element.
HTML
<div id="div-name">the section of the code i am working with has a countdown timer, when it reaches 0 i need to know if the mouse is over a specific box</div>
<button id="notification" onclick="javascript: letsCountIt(5);">click to start countdown</button>
JS
window.ev = false;
document.getElementById('div-name').onmouseover = function () {
window.ev = true;
console.log(window.ev);
}
document.getElementById('div-name').onmouseout = function () {
window.ev = false;
console.log(window.ev);
}
window.letsCountIt = function (cdtimer) {
cdtimer--;
document.getElementById('notification').innerHTML = cdtimer;
if (cdtimer == 0) {
if (window.ev === true) {
alert('over');
} else {
alert('not over');
}
} else {
setTimeout(function(){letsCountIt(cdtimer);}, 1000);
}
}
Look into document.elementFromPoint . When you pass an x,y to elementFromPoint, it will return whatever element (or <body>, if no other specific element) is at that point. You can easily check if this element is the element you want.
The problem then is finding out what point your mouse is at. How to get the mouse position without events (without moving the mouse)? seems to say - don't. At least use mouseMove to track the cursor. The linked question gives examples of how to do so. (Look to the lower scoring answers, as the higher ones only got points for being snarky.)
Just want to say that, I think jQuery's mouseenter and mouseleave events would make this a lot easier, but if you can't use them, maybe this will help you.
Depending on how your page is laid out, this may not be too difficult. You can get the position of your element using the following. Quoting from another answer
element.offsetLeft and element.offsetTop are the pure javascript
properties for finding an element's position with respect to its
offsetParent; being the nearest parent element with a position of
relative or absolute
So, if your element is positioned relatively to the body, so far so good (We don't need to adjust anything).
Now, if we attach an event to the document mousemove event, we can get the current coordinates of the mouse:
document.addEventListener('mousemove', function (e) {
var x = e.clientX;
var y = e.clientY;
}, false);
Now we just need to determine if the mouse falls within the element. To do that we need the height and width of the element. Quoting from another answer
You should use the .offsetWidth and .offsetHeight properties. Note
they belong to the element, not .style.
For example:
var element = document.getElementById('element');
var height = element.offsetHeight;
var width = element.offsetWidth;
Now we have all the information we need, and just need to determine if the mouse falls within the element. We might use something like this:
var onmove = function(e) {
var minX = element.offsetLeft;
var maxX = minX + element.offsetWidth;
var minY = element.offsetTop;
var maxY = minY + element.offsetHeight;
if(e.clientX >= minX && e.clientX <= maxX)
//good horizontally
if(e.clientY >= minY && e.clientY <= maxY)
//good vertically
}
This code works, but the mouse has to be moved once after page load.
var coords;
var getMouseCoordinates = function (e) {
'use strict';
return {
x: e.clientX,
y: e.clientY
};
};
document.addEventListener('mousemove', function (e) {
coords = getMouseCoordinates(e);
}, false);
document.addEventListener('click', function () {
var divCoords = document.getElementById('t').getBoundingClientRect();
if (coords.x >= divCoords.left && coords.x <= divCoords.right && coords.y >= divCoords.top && coords.y <= divCoords.bottom) {
alert('Mouse in box');
} else {
alert('Mouse not in box');
}
}, false);
You wouldn't bind to the click event of document, but rather the finish event of your countdown.
Here's an example. Try clicking in the output window.
You don't need any coordinates or mouse events, if you know a selector for that element:
if (document.querySelector('#elementSelector:hover')) {
alert('I like it when you touch me!');
}

JavaScript cursor acting odd

Setup
I'm making an HTML page that replaces the cursor with a div element. The Javascript is below. The div element is simply <div id="cursor"/>.
function fixCursor()
{
var cPos = getCursorPosition();
cursor.style="top:" + (cPos.y) + "; left:" + (cPos.x) + "; position:fixed; width:16px; height:16px; background-color:#DDDDDD; box-shadow: 2px 2px 4px rgba(0,0,0,.5); border:2px solid #111111; border-radius:0 100% 100% 100%;";
return;
}
function getCursorPosition(e) {
e = e || window.event;
var cursor = {x:0, y:0};
if (e.pageX || e.pageY) {
cursor.x = e.pageX;
cursor.y = e.pageY;
}
else if (e.screenX || e.ecreenY) {
cursor.x = e.screenX;
cursor.y = e.screenY;
}
else if (e.x || e.y) {
cursor.x = e.x;
cursor.y = e.y;
}
else if (e.clientX || e.clientY) {
var de = document.documentElement;
var b = document.body;
cursor.x = e.clientX;
cursor.y = e.clientY;
}
return cursor;
}
Problem
This only works on Opera, and shows signs of working on IE, but doesn't show the cursor. On Firefox and Chrome, nothing appears. I haven't tried Safari, as I uninstalled it a while ago, but in my experience, its rendering works alot like Chrome, anyway.
In your code, getCursorPosition takes an event object, e. In fixCursor, you are not passing anything in. You should probably make fixCursor take an event object as well and pass it through to getCursorPosition. Then, in your event handler where you're presumably calling fixCursor, pass in the event object passed into your event handler.
Also, you cannot set style equal to a string. You can, however, set style.cssText.
You can add custom cursors with the CSS cursor property, all major browsers but Opera support it to one extent or another. If you want it to work in IE you'll need to use a .cur or .ani cursor file, the other browsers support at minimum .cur, .png, .jpg and .gif.
Alternatively this answer points to a jQuery plugin that might be easier to use than implementing it yourself.
Personally I've never found a situation where I had to use a custom cursor, I usually just use a .png cursor in my stylesheet and leave a sensible default value for the browsers that don't support .png cursors.
Are you planning to do this onmousemove? That's a lot of overhead. In any case, I see a few problems, but I don't know if fixing them alone will get you a cross-browser solution.
First, let's assume you're triggering this function onmousemove, so you have this in your script:
document.onmousemove = fixCursor;
You have to have fixCursor() pass an event object to getCursorPosition() like this:
function fixCursor(e)
{
var cPos = getCursorPosition(e);
...
And you have to explicitly set each style attribute:
cursor.style.cursor = 'none'; // Didn't see you set this
cursor.style.top = cPos.y;
cursor.style.left = cPos.x;
cursor.style.position = "fixed";
A quick test showed me that this worked in Firefox but not IE. Setting position to "absolute" got closer in IE, but not in a useful way.
EDIT: Oh, and it appears you're not properly referencing the "cursor" div in your fixCursor() function. Use
var cursor = document.getElementById('cursor');

Categories

Resources