I've written a little drag and drop script in Javascript, I'm aiming to make it as efficient as possible (element following the cursor at the exact spot it was grabbed and no lag spikes) to accomplish this you usually try to limit the amount of times your move function gets fired (throttling the mousemove event) but I tried some other method, thing is I'm not sure if it's actualy doing what I'm expecting.
This is how I'm handling it;
Firstly, on mousedown, a requestAnimationFrame loop starts
var handleMovement = function(){
self.ele.style.top = self.dragElPos.pageTop+'px';
self.ele.style.left = self.dragElPos.pageLeft+'px';
self.animFrame = requestAnimationFrame(handleMovement);
}
And secondly, the mousemove event calls this function
var setCoordinates = function (e) {
self.dragElPos.pageTop = (e.pageY-self.dragElPos.innerTop);
self.dragElPos.pageLeft = (e.pageX-self.dragElPos.innerLeft);
}
All this does is store the position in some object, the handleMovement() function retrieves this and due to the animation loop the element gets moved.
The behaviour I'm expecting is that requestAnimationFrame is optimizing the loop so the animation runs smoothly, but because the coordinates are being set by some other function I wonder if it knows when it's optimized.
It looks pretty smooth, but if it's not being optimized I'd rather use mousemove throttling.
P.S. Browser compatibility is no concern.
Related
Edit
I'm essentially trying to create the Mario style jump, so as you touch / mousedown on the body I have an object that starts travelling up, but when you let go this acceleration stops. This means I can't use FastClick as I'm looking for touchstart, touchend events, not a single click event.
~
I'm trying to respond to a touchstart event on mobile in browser. At the moment I'm using these two listeners:
document.body.addEventListener('touchstart', function(e) {
e.preventDefault();
space_on();
return false;
}, false);
document.body.addEventListener('touchend', function(e) {
e.preventDefault();
space_off();
return false;
}, false);
I'm essentially trying to emulate something I've had working really well where I use keydown and keyup events to make a box jump and fall respectively.
The issue I'm having is that a touch start, if you don't swipe, is actually delaying for a short while. Either that, or a calculation is making me lose framerate.
I'm already using fastclick and that isn't effecting this (probably because it was never meant to fire on touchstart listeners). You can see what I mean here:
https://www.youtube.com/watch?v=8GgjSFgtmFk
I swipe 3 times and the box jumps immediately, and then I click 3 times and you can see (especially on the second one) it loses framerate a little bit or is delayed. Here is another, possibly clearer example: https://www.youtube.com/watch?v=BAPw1M2Yfig
There is a demo here:
http://codepen.io/EightArmsHQ/live/7d851f0e1d3a274b57221dac9aebc16a/
Just please bear in mind that you'll need to either be on a phone or touch device or emulate touches in chrome.
Can anyone help me lose the framerate drop or delay that is experienced on a touchstart that doesn't turn into a swipe?
You should not write your animation loop using setInterval.
Try to replace it with requestAnimationFrame, like this:
function render() {
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
draw_rects();
move();
fall();
move_scenery();
move_jumper();
jumper.y += jumper.vy;
requestAnimationFrame(render);
}
$(window).load(function() {
requestAnimationFrame(render);
});
Like I've done in this pen.
Now your render function is called as soon as the browser is ready to render a new frame. Note that this implementation doesn't use your fps variable, so your frame rate now depends on the browser/device speed. I tested the pen I've edited on my phone and now the animation is way smoother but the scenery is moving too fast (for me at least).
If you want a constant frame rate you can throttle it as explained, for example, here.
Note that you really don't need Fastclick because you aren't binding any click event in your code.
In Javascript, is it possible in a mousemove event handler to determine if there are any pending mousemove events in the queue?
Or, preferably, is it possible to make the event queue skip all mousemove events in the queue except for the most recent one?
I'm using mousemove & other mouse events to allow users to drag elements around the screen, and I don't need to waste CPU & GPU cycles redrawing the dragged items on every mousemove event (in which I update the top & left CSS properties), as long as I can skip intermediate events quickly enough.
My site is not currently experiencing any performance or visualization problems; I just want to make my JavaScript as efficient as possible.
Use event debouncing. This small throttle/debounce library works well with or without jQuery.
Example:
function someCallback() {
// snip
}
// don't fire more than 10 times per second
var debouncedCallback = Cowboy.debounce(100, someCallback);
document.getElementById('some-id').addEventListener('mousemove', debouncedCallback);
You could keep track of the last time you handled a mousemove, then skip the current one if it's too soon.
For example:
elem.onmousemove = function() {
var lastcalled = arguments.callee.lastCallTime || 0,
now = new Date().getTime();
if( now-lastcalled < 250) return;
arguments.callee.lastCallTime = now;
// rest of code here
};
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/
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.)
I want to get hold of the mouse position in a timeout callback.
As far as I can tell, this can't be done directly. One work around might be to set an onmousemove event on document.body and to save this position, fetching later. This would be rather expensive however, and isn't the cleanest of approaches.
I think you'll have to do the same thing as #Oli, but then if you're using jQuery, it would be much more easier.
http://docs.jquery.com/Tutorials:Mouse_Position
<script type="text/javascript">
jQuery(document).ready(function(){
$().mousemove(function(e){
$('#status').html(e.pageX +', '+ e.pageY);
});
})
</script>
The direct answer is no but as you correctly say, you can attach events to everything and poll accordingly. It would be expensive to do serious programming on every onmousemove instance so you might find it better to create several zones around the page and poll for a onmouseover event.
Another alternative would be to (and I'm not sure if this works at all) set a recurring timeout to:
Add a onmousemove handler to body
Move the entire page (eg change the body's margin-top)
when the onmousemove handler triggers, get the position and remove the handler. You might need a timeout on this event too (for when the mouse is out of the viewport)
Uberhack but it might work.
As you described, the most straightforward way to do it is setting the onmousemove event handler on the body. It's less expensive than you think: very little computation is done to store the coordinates, and the event gets fired 50 to 100 times a second when the mouse moves. And I suspect a typical user won't move their mouse constantly when viewing a web page.
The following script helps with counting event handlers; on my machine, moving the mouse around in Firefox, this added 5% to 10% to my CPU usage.
<script type="text/javascript">
jQuery(document).ready(function(){
var count = 0;
$().mousemove(function(e){ count += 1; });
$().click(function(e){ $('#status').html(count); });
});
</script>