Getting mouse coordinates in SDK first-time right-click - javascript

For an SDK add-on, I am trying to detect, on right-click, the closest page anchor (as described here).
Since JavaScript apparently has no way to query the mouse coordinates without first creating a mouse event listener, and since I don't want an expensive mouseover listener running all of the time on every single page! (and polling to rebuild a mouse listener is ugly and not fully accurate anyways), I was hoping I could get the mouse coordinates from a click event. Unfortunately, although self.on('click' does fire:
This event is without an event object from which mouse coordinates could be obtained
Adding a normal window.addEventListener('click',... listener doesn't actually get fired in the SDK content script with the first menu selection for some reason (it does fire subsequently, however).
Here is my workaround, but I want to fire on the current exact coordinates, not the coordinates of the node:
var x, y;
window.addEventListener('click', function (e) {
if (e.button === 2) {
x = e.clientX;
y = e.clientY;
}
}, true);
self.on('click', function (node) {
if (!x) { // Since this is not showing the first time, we fire on the node (which is specifically an element, not a text node), though we really want the real clientX and clientY where the mouse is, not this simulation based on where the element is
node.dispatchEvent(new MouseEvent('click', {
button: 2
}));
}
// Use document.caretPositionFromPoint with x and y to determine location of text
Is there any other way to get the actual mouse coordinates?

You can actually query the mouse coordinates without a mouse listener.
This page has a bunch of examples: https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes/Standard_OS_Libraries
It has to be done on per OS basis though as it uses JS-Ctypes. If you're worried about performance don't fear at all. You can use the code asynchronously via ChromeWorkers

Related

How to identify if a mouseover event object came from a touchscreen touch?

On virtually all current browsers (extensive details from patrickhlauke on github, which I summarised in an SO answer, and also some more info from QuirksMode), touchscreen touches trigger mouseover events (sometimes creating an invisible pseudo-cursor that stays where the user touched until they touch elsewhere).
Sometimes this causes undesirable behaviour in cases where touch/click and mouseover are intended to do different things.
From inside a function responding to a mouseover event, that has been passed the event object, is there any way I can check if this was a "real" mouseover from a moving cursor that moved from outside an element to inside it, or if it was caused by this touchscreen behaviour from a touchscreen touch?
The event object looks identical. For example, on chrome, a mouseover event caused by a user touching a touchscreen has type: "mouseover" and nothing I can see that would identify it as touch related.
I had the idea of binding an event to touchstart that alters mouseover events then an event to touchend that removes this alteration. Unfortunately, this doesn't work, because the event order appears to be touchstart → touchend → mouseover → click (I can't attach the normalise-mouseover function to click without messing up other functionality).
I'd expected this question to have been asked before but existing questions don't quite cut it:
How to handle mouseover and mouseleave events in Windows 8.1 Touchscreen is about C# / ASP.Net applications on Windows, not web pages in a browser
JQuery .on(“click”) triggers “mouseover” on touch device is similar but is about jQuery and the answer is a bad approach (guessing a hard-coded list of touchscreen user agents, which would break when new device UAs are created, and which falsely assumes all devices are mouse or touchscreen)
Preventing touch from generating mouseOver and mouseMove events in Android browser is the closest I could find, but it is only about Android, is about preventing not identifying mouseover on touch, and has no answer
Browser handling mouseover event for touch devices causes wrong click event to fire is related, but they're trying to elumate the iOS two-tap interaction pattern, and also the only answer makes that mistake of assuming that touches and mouse/clicks are mutually exclusive.
The best I can think of is to have a touch event that sets some globally accessible variable flag like, say, window.touchedRecently = true; on touchstart but not click, then removes this flag after, say, a 500ms setTimeout. This is an ugly hack though.
Note - we cannot assume that touchscreen devices have no mouse-like roving cursor or visa versa, because there are many devices that use a touchscreen and mouse-like pen that moves a cursor while hovering near the screen, or that use a touchscreen and a mouse (e.g. touchscreen laptops). More details in my answer to How do I detect whether a browser supports mouseover events?.
Note #2 - this is not a jQuery question, my events are coming from Raphael.js paths for which jQuery isn't an option and which give a plain vanilla browser event object. If there is a Raphael-specific solution I'd accept that, but it's very unlikely and a raw-javascript solution would be better.
Given the complexity of the issue, I thought it was worth detailing the issues and edge cases involved in any potential solution.
The issues:
1 - Different implementations of touch events across devices and browsers. What works for some will definitely not work for others. You only need to glance at those patrickhlauke resources to get an idea of how differently the process of tapping a touch-screen is currently handled across devices and browsers.
2 - The event handler gives no clue as to its initial trigger. You are also absolutely right in saying that the event object is identical (certainly in the vast majority of cases) between mouse events dispatched by interaction with a mouse, and mouse events dispatched by a touch interaction.
3 - Any solution to this problem which covers all devices could well be short-lived as the current W3C Recommendations do not go into enough detail on how touch/click events should be handled (https://www.w3.org/TR/touch-events/), so browsers will continue to have different implementations. It also appears that the Touch Events standards document has not changed in the past 5 years, so this isn't going to fix itself soon. https://www.w3.org/standards/history/touch-events
4 - Ideally, solutions should not use timeouts as there is no defined time from touch event to mouse event, and given the spec, there most probably won't be any time soon. Unfortunately, timeouts are almost inevitable as I will explain later.
A future solution:
In the future, the solution will probably be to use Pointer Events instead of mouse / touch events as these give us the pointerType (https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events), but unfortunately we're not there yet in terms of an established standard, and so cross-browser compatibility (https://caniuse.com/#search=pointer%20events) is poor.
How do we solve this at the moment
If we accept that:
You can't detect a touchscreen (http://www.stucox.com/blog/you-cant-detect-a-touchscreen/)
Even if we could, there's still the issue of non-touch events on a touch capable screen
Then we can only use data about the mouse event itself to determine its origin. As we've established, the browser doesn't provide this, so we need to add it ourselves. The only way to do this is using the touch events which are triggered around the same time as the mouse event.
Looking at the patrickhlauke resources again, we can make some statements:
mouseover is always followed by the click events mousedown mouseup and click - always in that order. (Sometimes separated by other events). This is backed up by the W3C recommendations: https://www.w3.org/TR/touch-events/.
For most devices / browsers, the mouseover event is always preceded by either pointerover, its MS counterpart MSPointerOver, or touchstart
The devices / browsers whose event order begins with mouseover have to be ignored. We can't establish that the mouse event was triggered by a touch event before the touch event itself has been triggered.
Given this, we could set a flag during pointerover, MSPointerOver, and touchstart, and remove it during one of the click events. This would work well, except for a handfull of cases:
event.preventDefault is called on one of the touch events - the flag will never be unset as the click events will not be called, and so any future genuine click events on this element would still be marked as a touch event
if the target element is moved during the event. The W3C Recommendations state
If the contents of the document have changed during processing of the
touch events, then the user agent may dispatch the mouse events to a
different target than the touch events.
Unfortunately this means that we will always need to use timeouts. To my knowledge there is no way of either establishing when a touch event has called event.preventDefault, nor understanding when the touch element has been moved within the DOM and the click event triggered on another element.
I think this is a fascinating scenario, so this answer will be amended shortly to contain a recommended code response. For now, I would recommend the answer provided by #ibowankenobi or the answer provided by #Manuel Otto.
What we do know is:
When the user uses no mouse
the mouseover is directly (within 800ms) fired after either a touchend or a
touchstart (if the user tapped and held).
the position of the mouseover and the touchstart/touchend are identical.
When the user uses a mouse/pen
The mouseover is fired before the touch events, even if not, the position of the mouseover will not match the touch events' position 99% of time.
Keeping these points in mind, I made a snippet, which will add a flag triggeredByTouch = true to the event if the listed conditions are met. Additionally you can add this behaviour to other mouse events or set kill = true in order to discard mouseevents triggered by touch completely.
(function (target){
var keep_ms = 1000 // how long to keep the touchevents
var kill = false // wether to kill any mouse events triggered by touch
var touchpoints = []
function registerTouch(e){
var touch = e.touches[0] || e.changedTouches[0]
var point = {x:touch.pageX,y:touch.pageY}
touchpoints.push(point)
setTimeout(function (){
// remove touchpoint from list after keep_ms
touchpoints.splice(touchpoints.indexOf(point),1)
},keep_ms)
}
function handleMouseEvent(e){
for(var i in touchpoints){
//check if mouseevent's position is (almost) identical to any previously registered touch events' positions
if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
//set flag on event
e.triggeredByTouch = true
//if wanted, kill the event
if(kill){
e.cancel = true
e.returnValue = false
e.cancelBubble = true
e.preventDefault()
e.stopPropagation()
}
return
}
}
}
target.addEventListener('touchstart',registerTouch,true)
target.addEventListener('touchend',registerTouch,true)
// which mouse events to monitor
target.addEventListener('mouseover',handleMouseEvent,true)
//target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)
Try it out:
function onMouseOver(e){
console.log('triggered by touch:',e.triggeredByTouch ? 'yes' : 'no')
}
(function (target){
var keep_ms = 1000 // how long to keep the touchevents
var kill = false // wether to kill any mouse events triggered by touch
var touchpoints = []
function registerTouch(e){
var touch = e.touches[0] || e.changedTouches[0]
var point = {x:touch.pageX,y:touch.pageY}
touchpoints.push(point)
setTimeout(function (){
// remove touchpoint from list after keep_ms
touchpoints.splice(touchpoints.indexOf(point),1)
},keep_ms)
}
function handleMouseEvent(e){
for(var i in touchpoints){
//check if mouseevent's position is (almost) identical to any previously registered touch events' positions
if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
//set flag on event
e.triggeredByTouch = true
//if wanted, kill the event
if(kill){
e.cancel = true
e.returnValue = false
e.cancelBubble = true
e.preventDefault()
e.stopPropagation()
}
return
}
}
}
target.addEventListener('touchstart',registerTouch,true)
target.addEventListener('touchend',registerTouch,true)
// which mouse events to monitor
target.addEventListener('mouseover',handleMouseEvent,true)
//target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)
a{
font-family: Helvatica, Arial;
font-size: 21pt;
}
Click me
According to https://www.html5rocks.com/en/mobile/touchandmouse/
For a single click the order of events is:
touchstart
touchmove
touchend
mouseover
mousemove
mousedown
mouseup
click
So you might be able to set some arbitrary boolean isFromTouchEvent = true; in onTouchStart() and isFromTouchEvent = false; in onClick() and check for that inside of onMouseOver(). This doesn't work very well since we're not guaranteed to get all those events in the element that we're trying to listen on.
I usually have couple of general schemes which I use for this, one of them uses a manual principle of setTimeout to trigger a property. I will explain this one here, but first try to reason about using touchstart, touchmove and touchend on touch devices and use mouseover on destop.
As you know, calling event.preventDefault (event has to be not passive for this to work with touchstart) in any of the touchevents will cancel the subsequent mousecalls so you do not need to deal with them. But in case this is not what you want, here is what I use sometimes (I refer as "library" to your dom manipulation library, and "elem" as your element):
with setTimeout
library.select(elem) //select the element
.property("_detectTouch",function(){//add a _detectTouch method that will set a property on the element for an arbitrary time
return function(){
this._touchDetected = true;
clearTimeout(this._timeout);
this._timeout = setTimeout(function(self){
self._touchDetected = false;//set this accordingly, I deal with either touch or desktop so I can make this 10000. Otherwise make it ~400ms. (iOS mouse emulation delay is around 300ms)
},10000,this);
}
}).on("click",function(){
/*some action*/
}).on("mouseover",function(){
if (this._touchDetected) {
/*coming from touch device*/
} else {
/*desktop*/
}
}).on("touchstart",function(){
this._detectTouch();//the property method as described at the beginning
toggleClass(document.body,"lock-scroll",true);//disable scroll on body by overflow-y hidden;
}).on("touchmove",function(){
disableScroll();//if the above overflow-y hidden don't work, another function to disable scroll on iOS.
}).on("touchend",function(){
library.event.preventDefault();//now we call this, if you do this on touchstart chrome will complain (unless not passive)
this._detectTouch();
var touchObj = library.event.tagetTouches && library.event.tagetTouches.length
? library.event.tagetTouches[0]
: library.event.changedTouches[0];
if (elem.contains(document.elementFromPoint(touchObj.clientX,touchObj.clientY))) {//check if we are still on the element.
this.click();//click will never be fired since default prevented, so we call it here. Alternatively add the same function ref to this event.
}
toggleClass(document.body,"lock-scroll",false);//enable scroll
enableScroll();//enableScroll
})
Another option without setTimeout is to think mousover is counter to touchstart and mouseout counter to touchend. So former events (the touch events) will set a property, if the mouse events detect that property then they do not fire and reset the property to its initial value and so on. In that case something along these lines will also do:
without setTimeout
....
.on("mouseover",function(dd,ii){
if (this._touchStarted) {//touch device
this._touchStarted = false;//set it back to false, so that next round it can fire incase touch is not detected.
return;
}
/*desktop*/
})
.on("mouseout",function(dd,ii){//same as above
if(this._touchEnded){
this._touchEnded = false;
return;
}
})
.on("touchstart",function(dd,ii){
this._touchStarted = true;
/*some action*/
})
.on("touchend",function(dd,ii){
library.event.preventDefault();//at this point emulations should not fire at all, but incase they do, we have the attached properties
this._touchEnded = true;
/*some action*/
});
I removed a lot of details but I guess this is the main idea.
You can use modernizr for that! I just tested this on a local development server and it works.
if (Modernizr.touch) {
console.log('Touch Screen');
} else {
console.log('No Touch Screen');
}
So I would start there?
Pointer Events are widely supported now. So now we can use pointerenter and check event.pointerType:
const element = document.getElementById("hoverableElement")
element.addEventListener("pointerenter", (event) => {
if (event.pointerType === "mouse") {
alert("Hovered")
}
})
<div id="hoverableElement">Trigger on hover, but not on touch</div>

Triggering mousemove on the mouse's current position

Suppose we have a <div> with a mousemove handler bound to it. If the mouse pointer enters and moves around this div, the event is triggered.
However, I am dealing with a rich web application where <div>s move around the screen, appear and disappear... So it may happen that a <div> appears under the mouse pointer. In this case, mousemove is not triggered. However, I need it to be. (Note that replacing mousemove with mouseover does not change this behavior.)
Specifically, the <div> has to be highlighted and I deem it as a UI flaw to require the user to do a slight mouse move in order to trigger the highlighting.
Is it possible to trigger the mousemove event programatically? And I do not mean
document.getElementById('mydiv').onmousemove();
because onmousemove is parametrised by the event object, which I do not have.
Is it possible to make browser behave as if onmousemove was triggered on the current mouse's position (although in fact the mouse didn't move)?
You could modify your mousemove to keep a state variable with the current mouse coordinates, and use that information to perform a collision detection that you call both on mouse move, and on moving a div.
A little example of what that might look like
You actually can create a mousemove event object to pass in, using something like this:
window.onload = function () {
document.getElementById("test").onmousemove = function(e) { console.log(e); };
document.getElementById("test").onclick = function(e) {
var e = document.createEvent('MouseEvents');
e.initMouseEvent('mousemove',true,true,document.defaultView,<detail>,<screenX>,<screenY>,<mouseX>,<mouseY>,false,false,false,false,<button>,null);
this.onmousemove(e);
};
};
Of course, here I'm firing it on a click, but you can do it on whatever event you want, such as when your div becomes visible, check to see if the mouse is within it. You just need to make sure your parameters are right, and you need to track the mouse position on your own. Also, there's some differences in IE, I think. Here's my source: http://chamnapchhorn.blogspot.com/2008/06/artificial-mouse-events-in-javascript.html. He added a little extra code to account for it.
Here's a fiddle to play around with. http://jsfiddle.net/grimertop90/LxT7V/1/

Duplicate events in prototype

I have a setup where I have a grid of elements and when I rollover each element a popup appears like an advanced tooltip. I first check if the popup needs to follow the moused over elements mouse position, if so I use a mousemove event. I first stopObserving in case there was one set before, then I start observing. Do I really need to do this or is prototype smart enough to not to add duplicate events on the same element.
show:function(param){
if(this.isFollow){
$(param.target).stopObserving('mousemove', this.onMouseMove);
$(param.target).observe('mousemove', this.onMouseMove);
}
},
//param.target is the element that is being rolled over. I pass this in to my show method to then find its x and y position.
onMouseMove:function(event){
var xPos = Event.pointerX(event);
var yPos = Event.pointerY(event);
_self._popup.setStyle({left: xPos + 10 + "px", top:yPos + 10 + "px"});
}
Second question. When I move my mouse across the elements really fast my popup that is following the mouse sometimes lags and the mouse goes over the popup obstructing the mouseover event on the element below it.
I presume this is the nature of the mousemove as its not rendering fast enough. Should I be using setTimeout or something like that instead of mousemove, to prevent this lag.
1) No, Prototype won't set the same event handler twice. It'll only happen if you declare your handler function in-line (i.e. element.observe('click', function(){…})) since the handler will be sent a newly created function each time, and never the exact same instance of a function.
But in your case, where you're referring to the onMouseMove function, Prototype will check whether that particular function is already registered for that particular event, on that particular element. And if it is, it won't be registered again.
2) You can't avoid the lag on fast mouse movements, no. The browser won't send the mousemove events fast enough. You could use a timer, but I'd probably try registering a single mousemove handler for the parent element of all the grid-elements (or maybe even document itself), and use the X/Y coordinates to figure out which grid-element to show the tooltip for. Then you don't have to bother with setting event handlers for each element. I.e. if the grid was a standard table, I'd listen for events on the <table> element itself, rather than on each and every <td>. Especially if you still want to implement a timer, I should think, it'd be easier to deal with everything in one place (otherwise, a timer might accidentally execute on some element you've already moused out of, and your tooltip will flicker back and forth or something. If you only want 1 tooltip at a time, it's easier to manage it in 1 place.)

Detect if mouse is over an element when page loads with Javascript

I have an image that I want to have trigger certain behaviors when the mouse is over, I have a mouseover and mouseout method, but if you happen to have your mouse over the image when the page loads, the mouseover method never fires until you leave the image and come back over it.
Is there a way to detect if the mouse is over an element on the fly without the mouse having to be off of the element and then come over the element to trigger the JS mouseover event? Like is there a document.getElementById("blah").mouseIsOver() type function in Javascript?
I believe this is possible without any action from the user. When your page loads, bind the mouseover event to your image and hide your image (i.e. using CSS display:none). Use setTimeout() to show it again in a few milliseconds (10 should be enough). The even should be fired.
If you don't want to cause the 'flick' effect on your image, you may try using some temporary element instead, attaching event to it, and delegating the event onto your image.
I have no idea if this is cross-browser solution, but it worked from my Firefox 3.0 console ;)
You could use the mousemove event. That would trigger anytime the user moves a mouse; so the only instance of the trigger not firing would be if the user does not move the mouse at all, which should be rare.
The only problem with this is that the event would fire anytime the mouse would move over your image, so you would get a LOT of those events while over the component. What you would probably need to do is implement some sort of flag within your method when the event fires. You turn on the flag when the event first fires, and you turn it off when you leave the component.
This is less than ideal, but I think this will probably satisfy your problem scenario. The following is some quick pseudo code on what that solution might look like, I think it should work.
<img src="blah.png" onmousemove="JavaScript:triggerOn(event)" onmouseout="JavaScript:triggerOff(event)"/>
...
<script type='text/javascript'>
var TriggerActive = false;
function triggerOn(e){
e = e||window.e;
if( !TriggerActive){
TriggerActive = true;
// Do something
} else {
// Trigger already fired, ignore this event.
}
}
function triggerOff(e){
e = e||window.e;
if(TriggerActive)
TriggerActive = false;
}
</script>
You can find some great mouse event information including browser compatibility notes here.
Use document.querySelectpor and onload/onready events.
var a = document.querySelector('#a:hover');
if (a) {
// Mouse cursor is above a
}
else {
// Mouse cursor is outside a
}
There is no way to get the mouse coordinates aside from listening for mouse events, namely mousemove, mouseover etc. However, these events are very sensitive in the sense that moving the cursor by just one pixel is enough to trigger them, so having the cursor hover over your image while perfectly still should be somewhat unusual.

JavaScript: Create event with current mouse coordinates

I was looking on this post but it doesn't really help me. What I'm trying to do is to create event, or what would be even better, access current mouse screen coordinates.
I have a function with setTimeout inside it, where number of different checks on attributes are performed. In my program some of the elements are changing position and what I want to do is check whether mouse is still over some elements or not.
Many thanks,
Artur
You just want to bind to the onmouseover and onmouseout events of an object. I found this to be unreliable however as some users can't keep a cursor over a small target so I found a great plug-in for jQuery called hoverIntent that works beautifully.
This solves the problem:
// Mouse coordinates for event.clientX, event.clientY respectively.
var myClientX, myClientY;
// This call makes sure that global variables myClientX, myClientY are up to date
window.document.addEventListener("mousemove", function(event) {
myClientX = event.clientX;
myClientY = event.clientY;
}, false);
Making coordinates global let's me access them whenever I want :)

Categories

Resources