Why can't modern browsers keep up with the mouse? Firefox 3.6, Chrome 8.xx, Opera 10.xx, all fail to fire mouse events like mouseover, mouseout and mousemove at anything like a high enough rate to keep up with a fast moving mouse.
How does the browser decide when to generate a mouseover or mouseout event under these conditions? In practice it bears no resemblance to the accepted definition of "This mouseout event is sent to an element when the user moves the mouse outside the element. This event is the reverse of mouseover." [MDC]
The code below is the simplest case I can find to demonstrate this behaviour. Event listeners are attached to the document object.
<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en" lang="en" >
<head>
<title>Mouse event tracking</title>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<style type="text/css">
div {
position: absolute;
border: 1px solid black;
width: 40px;
height: 100px;
}
</style>
</head>
<body>
<h1 style="position: absolute; top: 10px; left: 10px; font-size: 14px">Start on green, move mouse to yellow</h1>
<div id="ladder_start" style="top: 50px; left: 10px; background-color: #44ff44;"></div>
<div id="ladder" style="top: 50px; left: 50px;"></div>
<div id="ladder_end" style="top: 50px; left: 850px; background-color: #ffff44;"></div>
<div id="ladder_p_over" style="top: 50px; left: 900px; width: 100px; height: 50px"></div>
<div id="ladder_p_out" style="top: 100px; left: 900px; width: 100px; height: 50px"></div>
<canvas id="ladder_trace" style="position: absolute; border: 1px solid black; top: 155px; left: 50px; height: 20px"></canvas>
<script type="text/javascript">
window.addEventListener("load", function (event) {
if (window.event_tests_loaded === true) {
return;
}
var ladder = document.getElementById("ladder");
var ladder_trace = document.getElementById("ladder_trace");
var ladder_start = document.getElementById("ladder_start");
var ladder_end = document.getElementById("ladder_end");
var ladder_p_over = document.getElementById("ladder_p_over");
var ladder_p_out = document.getElementById("ladder_p_out");
var mouse_over_count = 0;
var mouse_out_count = 0;
var steps = [];
var trace = ladder_trace.getContext('2d');
var step_over = function (event) {
event.preventDefault();
event.stopPropagation();
if (typeof event.target.parentNode === "object") {
if (event.target.parentNode === ladder) {
event.target.style.backgroundColor = event.target.highlight_color;
mouse_over_count++;
ladder_p_over.textContent = mouse_over_count;
}
}
};
var step_out = function (event) {
event.preventDefault();
event.stopPropagation();
if (typeof event.target.parentNode === "object") {
if (event.target.parentNode === ladder) {
mouse_out_count++;
ladder_p_out.textContent = mouse_out_count;
}
}
};
var move = function (event) {
event.preventDefault();
event.stopPropagation();
if ((event.clientX >= 50) && (event.clientX < 850)) {
trace.fillStyle = "black";
trace.strokeStyle = "black";
trace.lineWidth = 1;
trace.beginPath();
trace.moveTo(event.clientX - 50 + 0.5, 0);
trace.lineTo(event.clientX - 50 + 0.5, 20);
trace.stroke();
}
};
ladder_start.addEventListener("mouseover", function (event) {
event.preventDefault();
event.stopPropagation();
mouse_over_count = 0;
mouse_out_count = 0;
ladder_p_over.textContent = mouse_over_count;
ladder_p_out.textContent = mouse_out_count;
var i;
for (i = 0; i < 40; i++) {
steps[i].style.backgroundColor = steps[i].original_color;
}
trace.clearRect(0, 0, 800, 20);
document.addEventListener("mouseover", step_over, false);
document.addEventListener("mouseout", step_out, false);
document.addEventListener("mousemove", move, false);
}, false);
ladder_end.addEventListener("mouseover", function (event) {
document.removeEventListener("mouseover", step_over, false);
document.removeEventListener("mouseout", step_out, false);
document.removeEventListener("mousemove", move, false);
}, false);
(function () {
var i;
for (i = 0; i < 40; i++) {
steps[i] = document.createElement("div");
steps[i].style.width = "20px";
steps[i].style.border = "0";
steps[i].style.top = "0px";
steps[i].style.left = (i * 20) + "px";
steps[i].original_color = "rgb(" + (i * 4) + ",0,0)";
steps[i].highlight_color = "rgb(255," + (i * 4) + ",255)";
steps[i].style.backgroundColor = steps[i].original_color;
ladder.appendChild(steps[i]);
}
ladder.style.width = (i * 20) + "px";
ladder_trace.style.width = (i * 20) + "px";
ladder_trace.width = (i * 20);
ladder_trace.height = 20;
}());
ladder_p_over.textContent = mouse_over_count;
ladder_p_out.textContent = mouse_out_count;
}, false);
</script>
</body>
</html>
That would be because they render first, and then check for input.
I've been frustrated about this for a while too, but a few tests confirmed this.
They render and then check for input.
Edit: If you have a good computer and use a good recording software, try recording your mouse in your monitor's full FPS. Now play it in slow motion, like 1/10 speed.
You'll notice that the mouse cursor doesn't pass through every pixel on the screen. If it did, it could either...
Go through every pixel, every frame.
This would limit your mouse speed (pixels/second) to your monitor's FPS (hertz).
Virtually go through every pixel in a sub-frame manner.
This would definitely take much processor time to think.
To fix this, every OS makes the mouse jump/skip to the required pixel every frame.
... and thus, your problem.
Related
I've been waiting to ask this question for a long time, but couldn't, because I knew I would get bad reputation. This post was very hard to post, but I REALLY need this code...
Let's say there was a draggable element with the ID of "dragme"... You have to drag and drop the element to a specific spot. I was wondering if there is a code that does this task automatically for me when I execute a function. Lets name that function "dropElement". I am trying to drag "dragme" to my mouse position with a "dragElement" function with jquery or js.
This is what I tried:
(function() {
'use strict';
var mouseX = 0;
var mouseY = 0;
var timer = 0;
//tracks mouse position
document.body.addEventListener("mousemove", function(e) {mouseX = e.clientX; mouseY = e.clientY;});
function dropElement() {
$("#dragme").trigger($.Event("mousedown", {button: 0}));
$("body").trigger($.Event("mouseup", {button: 0, clientX: mouseX, clientY: mouseY}));
timer = setTimeout(drop, 100);
}
dropElement() //executes function and drops "dragme" to mouse position
I found the code in the question a bit complex to follow, especially with a timing function.
Instead I've gone back to basics (and vanilla JS) to think about the sequence of events. The user moves the mouse, we aren't interested unless they have put the mousedown within the element we want to drag. So this snippet sets a variable isDown which is set to true when the user puts the mouse down on the element.
Then it looks for a mousemove event on the whole window and if isDown is set it moves the element.
We also look for the mouseup event on the window and unset isDown.
The reason for looking for some events on the actual element and some on the window is because things are moving - the mouse may get out of the window before it is released for example.
let isDown = false;
const dragMe = document.querySelector('.dragme');
dragMe.addEventListener('mousedown', function() {
isDown = true;
});
window.addEventListener('mouseup', function() {
isDown = false;
});
window.addEventListener('mousemove', function() {
if (isDown) {
dragMe.style.top = event.clientY + 'px';
dragMe.style.left = event.clientX + 'px';
}
});
.dragme {
width: 5em;
height: 5em;
background-color: cyan;
position: relative;
top: 0;
left: 0;
}
<div class="dragme">Drag me</div>
I hope this sample helps you
var drag = {
elem: null,
x: 0,
y: 0,
state: false
};
var delta = {
x: 0,
y: 0
};
function dropElement(e){
var cur_offset = $("#autoDrag").offset();
$("#autoDrag").animate({
left: (e.pageX),
top: (e.pageY )
});
}
$(document).mousedown(function(e) {
dropElement(e);
})
$("#dragMe").mousedown(function(e) {
drag.elem = dragMe;
drag.x = e.pageX;
drag.y = e.pageY;
drag.state = true;
})
$(document).mousemove(function(e) {
if ( drag.state) {
delta.x = e.pageX - drag.x;
delta.y = e.pageY - drag.y;
var cur_offset = $(drag.elem).offset();
$(drag.elem).offset({
left: (cur_offset.left + delta.x),
top: (cur_offset.top + delta.y)
});
drag.x = e.pageX;
drag.y = e.pageY;
}
})
$("#dragMe").mouseup(function() {
drag.state = false;
})
#dragMe {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 80px;
height: 80px;
padding:10px;
background-color: #00a1ff;
color: white;
border-radius: 50px;
}
#autoDrag {
position: absolute;
right:0;
display: flex;
justify-content: center;
align-items: center;
text-align:center;
width: 80px;
height: 80px;
padding:10px;
background-color: #ff00ff;
color: white;
border-radius: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span id="dragMe">DragMe!</span>
<span id="autoDrag">Click somewhere I will be there!</span>
I have an array of keys that stores which keys are being pressed. Whenever I press the left arrow, spacebar, and the up arrow together, the array will only keep two of the keycodes, leaving one not to be pushed into the array. When these three keys are pressed, the character is supposed to move left, jump up, and shoot a bullet. One of those three actions won't occur. I am using Google Chrome, and I don't know what will happen on other browsers.
var $c = $('canvas');
var ctx = $c[0].getContext('2d');
var x = 20;
var y = 150;
var keys = [];
var bulletX = x + 2;
var bulletY = 0;
var bullets = [];
var face = 1;
function plr() {
ctx.fillStyle = 'black';
ctx.fillRect(x, y, 20, 20);
}
$c.keydown(function(e) {
if (_.includes(keys, e.which) === false) {
keys.push(e.which);
}
});
$c.keyup(function(e) {
_.pull(keys, e.which);
});
function shoot() {
bullets.forEach(function(bullet) {
ctx.fillStyle = 'red';
ctx.fillRect(bullet.bX, bullet.bY, 8, 4);
if (bullet.direction === 0) {
bullet.bX -= 7;
}
if (bullet.direction === 1) {
bullet.bX += 7;
}
if (bullet.bX > 700 || bullet.bX < 0) {
_.pull(bullets, bullet);
}
});
}
setInterval(function() {
ctx.clearRect(0, 0, 700, 500);
if (keys.includes(32)) {
bullets.push({
direction: face,
bX: x + face * 12,
bY: y + 8
});
}
if (keys.includes(37)) {
face = 0;
x -= 3;
}
if (keys.includes(38)) {
y-=3;
}
if (keys.includes(39)) {
face = 1;
x += 3;
}
plr();
shoot();
}, 30);
.canvas {
background-color: #a3c2ba;
outline: none;
border: #fff;
margin: auto;
display: block;
position: relative;
top: 50px;
border-radius: 0px;
}
body {
margin: 0;
}
<!DOCTYPE html>
<html>
<head>
<title>Game</title>
<script src='https://code.jquery.com/jquery-3.4.1.min.js'></script>
<script src='https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js'></script>
</head>
<body>
<canvas class='canvas' width='300' height='200' tabindex='1' />
</body>
</html>
I have concluded that this is a problem with my keyboard. I tried this on my brother's computer and it worked fine. Thanks to everyone who attempted to help though!
I have a drag and drop script that is relatively functional. However, I want to be able to trigger mouseup anywhere on the screen. Is there a way to trigger mouseup outside of the window, or outside of the current element? I know this is possible and I've seen other questions like this. I wanted to find a way in vanilla Javascript to detect mouseups like this.
document.onmousemove = mouseCoords;
var x = 0;
var y = 0;
var cl1= false;
var divid;
var offs1;
var offs2;
var topPos;
var leftPos;
function mouseCoords(e) {
x = e.x
y = e.y
if(cl1 === true){
document.getElementById(divid).style.top = topPos + (y-offs1) + 'px';
document.getElementById(divid).style.left = leftPos + (x-offs2) + 'px';
}
}
var drag = function(i, cas) {
divid= i
switch(cas){
case 1:
var rect = document.getElementById(divid).getBoundingClientRect();
leftPos = rect.left;
topPos = rect.top;
offs1 = y;
offs2 = x;
cl1= true;
break;
case 0:
offs1 = 0;
offs2 = 0;
cl1= false;
break;
}
}
#block{
width: 100px; z-index: 20; height: 50px; background-color: blue; position: fixed; user-select: none; -webkit-user-select: none;
}
.drag{
width: 200px; height: 100px; background-color: red; position: fixed;
}
<div id="block">mouseup doesn't trigger over me!</div>
<div id="1" class="drag" onmousedown="drag(1, 1)" onmouseup="drag(1, 0)"></div>
Use
document.addEventListener("mouseup", drag(null, 0));
for mouseup,
and this code for mousedown.
document.addEventListener("mousedown", drag(null, 1));
Basically, document.addEventListener works for the whole window. "mouseup" tells the script that the event is a mouseup, and the final bit is the function to be executed (drag(1, 0))
is there a way to get the touch position in a touchmove event every x milliseconds and then execute a function, when the x-coordinate at the moment and the one at the start are differing e.g. 50px?
Thanks
Try the below ;
$('document').ready(function() {
var touch,
action,
diffX,
diffY,
endX,
endY,
startX,
startY,
timer,
timerXseconds = 500, // Change to the Time(milliseconds) to check for touch position
xDifferenceX = 50, // Change to difference (px) for x-coordinates from starting point to run your function
xDifferenceY = 50; // Change to difference (px) for y-coordinates from starting point
function getCoord(e, c) {
return /touch/.test(e.type) ? (e.originalEvent || e).changedTouches[0]['page' + c] : e['page' + c];
}
function testTouch(e) {
if (e.type == 'touchstart') {
touch = true;
} else if (touch) {
touch = false;
return false;
}
return true;
}
function onStart(ev) {
if (testTouch(ev) && !action) {
action = true;
startX = getCoord(ev, 'X');
startY = getCoord(ev, 'Y');
diffX = 0;
diffY = 0;
timer = window.setInterval(checkPosition(ev), timerXseconds); // get coordinaties ever X time
if (ev.type == 'mousedown') {
$(document).on('mousemove', onMove).on('mouseup', onEnd);
}
}
}
function onMove(ev) {
if (action) {
checkPosition(ev)
}
}
function checkPosition(ev) {
endX = getCoord(ev, 'X');
endY = getCoord(ev, 'Y');
diffX = endX - startX;
diffY = endY - startY;
// Check if coordinates on Move are Different than Starting point by X pixels
if (Math.abs(diffX) > xDifferenceX || Math.abs(diffY) > xDifferenceY) {
// console.log('Start is :' + startX + ' End is : ' + endX + 'Difference is : ' + diffX);
$(this).trigger('touchend');
// here Add your function to run...
}
}
function onEnd(ev) {
window.clearInterval(timer);
if (action) {
action = false;
if (ev.type == 'mouseup') {
$(document).off('mousemove', onMove).off('mouseup', onEnd);
}
}
}
$('#monitor')
.bind('touchstart mousedown', onStart)
.bind('touchmove', onMove)
.bind('touchend touchcancel', onEnd);
});
body {
margin: 0;
padding: 0;
}
#monitor {
height: 500px;
width: 500px;
position: relative;
display: block;
left: 50px;
top: 50px;
background: green;
}
.box {
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: center;
font-weight: bold;
bottom: 0;
background: white;
width: 50px;
height: 50px;
margin: auto;
font-size: 16px;
line-height: 23px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='monitor'>
<div class='box'>start here</div>
</div>
Read this post for a more detailed answer
This can be done in a few functions.
The first function is called when there is a movement of the touch event, this event stores the x and y of the touch in a separate variable.
Then we have a function that runs every X miliseconds, this function gets the x and y from the move event and dispatches then to your code.
Functions 3, 4 and 5 are used to handle the start, stop and cancel dragevents, and start/stop the second function:
var timerid;
var x;
var y;
var tick = 0;
function handleStart(evt) {
console.log("handleStart");
evt.preventDefault();
timerid = window.setInterval(timer, 50); // Replace 50 here with X
}
function handleEnd(evt) {
console.log("handleEnd");
evt.preventDefault();
window.clearInterval(timerid);
}
function handleCancel(evt) {
console.log("handleCancel");
evt.preventDefault();
window.clearInterval(timerid);
}
function handleMove(evt) {
console.log("handleMove");
evt.preventDefault();
// Select last point:
var point = evt.changedTouches[evt.changedTouches.length - 1];
x = point.pageX;
y = point.pageY;
}
function timer() {
console.log("timer");
tick++;
document.getElementById("output").innerHTML = "tick: " + tick + " x: " + x + " y:" + y;
}
var el = document.getElementById("canvas");
el.addEventListener("touchstart", handleStart, false);
el.addEventListener("touchend", handleEnd, false);
el.addEventListener("touchcancel", handleCancel, false);
el.addEventListener("touchmove", handleMove, false);
<canvas id="canvas" width="300" height="300" style="border:solid black 1px;"></canvas>
<p id=output></p>
As long as the user is pressing the screen, the code will print out the x and the y coordinate to the screen. You can also integrate the reading of the x and y into your existing game loop instead of having a separate function if that is needed for your project.
Take a look at hammer.js, it has exactly what you need. It supports "touchmove" called pan, that is being called every few milliseconds when you pan. Also there is a threshold property which determine a length in pixels you have to pan before recognizing it as a pan.
I'm trying to create my own click and drag function in JavaScript without the use of jquery. I know that jquery is easy to implement, but I prefer my own code. What I have, as i click the div, then move the mouse, the div moves to the same spot and doesn't implement a "dragging" look to it. I'm not sure why this is. I want my outcome to be able to move the div over the image that way I can "crop" the image based on the div, etc. My code is:
index.js
function _(element) {
return document.getElementById(element);
}
index.css
body {
background-color: rgb(33, 66, 99);
margin: 0px;
padding: 0px;
}
img {
position:absolute;
}
.selection {
width: 200px;
height: 200px;
background-color: rgb(255,255,255);
position: absolute;
}
index.php
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8"/>
<title>Image Cropping</title>
<link rel = "stylesheet" href = "index.css"/>
<script src = "index.js"></script>
</head>
<body>
<div class = "image">
<img src = "model.jpg" alt = "Model" id = "theImage"/>
<div class = "selection" id = "selection"/>
</div>
<script>
_("theImage").ondragstart = function() { return false; };
var m = _("selection");
m.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mouseup", mouseUp, false);
function mouseUp() {
window.removeEventListener("mousemove", move, true);
}
function mouseDown(e) {
window.addEventListener("mousemove", move, true);
}
function move(e) {
var x = m.style.left;
var y = m.style.top;
var mouseX = e.clientX;
var mouseY = e.clientY;
m.style.top += (mouseX - x) + "px";
m.style.left += (mouseY - y) + "px";
// Also tried: m.style.top = (mouseX - x) + "px";
// And : m.style.left = (mouseY - y) + "px";
}
</script>
</body>
</html>
To add the "dragging look to it", you can:
change the cursor (cursor: move;)
keep the cursor's offset relative to the mouse
For the second one, I reused a function I created for one of my projects, for which I implemented drag and drop for mobile, not wanting to use a big library:
/*
* Returns the given element's offset relative to the document.
*/
function realOffset(elem) {
var top = 0, left = 0;
while (elem) {
top = top + parseInt(elem.offsetTop, 10);
left = left + parseInt(elem.offsetLeft, 10);
elem = elem.offsetParent;
}
return { top: top, left: left };
}
Using this function, the math becomes simple:
m.style.left = (mouseX - offset.left) + "px";
m.style.top = (mouseY - offset.top) + "px";
Full demo
_("theImage").ondragstart = function () { return false; };
var m = _("selection"), offset;
m.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mouseup", mouseUp, false);
function mouseUp() { window.removeEventListener("mousemove", move, true); }
function mouseDown(e) {
// SAVE THE OFFSET HERE
offset = {
left: e.pageX - realOffset(m).left,
top: e.pageY - realOffset(m).top
};
window.addEventListener("mousemove", move, true);
}
function move(e) {
// REUSE THE OFFSET HERE
m.style.left = (e.pageX - offset.left) + "px";
m.style.top = (e.pageY - offset.top) + "px";
}
/*
* Returns the given element's offset relative to the document.
*/
function realOffset(elem) {
var top = 0, left = 0;
while (elem) {
top = top + parseInt(elem.offsetTop, 10);
left = left + parseInt(elem.offsetLeft, 10);
elem = elem.offsetParent;
}
return { top: top, left: left };
}
function _(element) { return document.getElementById(element); }
body {
background-color: rgb(33, 66, 99);
margin: 0px;
padding: 0px;
}
img {
position:absolute;
}
.selection {
width: 200px;
height: 200px;
background-color: rgba(255,255,255,.5);
position: absolute;
cursor: move;
}
<div class="image">
<img src="http://i.imgur.com/vxkljMP.jpg" alt="Model" id="theImage" />
<div class="selection" id="selection"></div>
</div>