Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript move element with mousemove event 60 FPS requestAnimationFrame

there! i have a problem for getting #drag element moving smoothly.

i look at this article : http://www.html5rocks.com/en/tutorials/speed/animations/#debouncing-mouse-events

it said that : "the problem with mousemove event when moving element was mousemove event fired too much

so, i try to used their method : using requestAnimationFrame + boolean checking.

look at this fiddle for live action : https://jsfiddle.net/5f181w9t/

HTML :

<div id="drag">this is draggable</div>

CSS :

#drag {width:100px; height:50px; background-color:red; transform:translate3d(0, 0, 0); }

JS :

var el               = document.getElementById("drag"),
    startPosition    = 0, // start position mousedown event
    currentPosition  = 0, // count current translateX value
    distancePosition = 0, // count distance between "down" & "move" event
    isMouseDown      = false; // check if mouse is down or not 

function mouseDown(e) {
    e.preventDefault(); // reset default behavior
    isMouseDown     = true;
    startPosition   = e.pageX; // get position X
    currentPosition = getTranslateX(); // get current translateX value
    requestAnimationFrame(update); // request 60fps animation
}    

function mouseMove(e) {
    e.preventDefault();
    distancePosition = (e.pageX - startPosition) + currentPosition; // count it!  
}

function mouseUp(e) {
    e.preventDefault();
    isMouseDown = false; // reset mouse is down boolean
}

function getTranslateX() {
   var translateX = parseInt(getComputedStyle(el, null).getPropertyValue("transform").split(",")[4]);

   return translateX; // get translateX value

}

function update() {
    if (isMouseDown) { // check if mouse is down
        requestAnimationFrame(update); // request 60 fps animation
    }
    el.style.transform = "translate3d(" + distancePosition + "px, 0, 0)";
  // move it!
}

el.addEventListener("mousedown", mouseDown);
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);

is this the correct way to accompolished it?

what's wrong with my code?

thanks

like image 299
Ching Ching Avatar asked Mar 19 '16 04:03

Ching Ching


2 Answers

Your code should already work fine. However, here's another way to do it:

You need to make sure you only let one requestAnimationFrame call go through per frame, otherwise update() will be called multiple times at the next repaint, which can cause lag and decrease your fps. To do this, you want to save the requested frame and on each mousemove event check if there's already a frame lined up. If there is, you'll want to use cancelAnimationFrame to cancel it and make a new request. This way update() is only called as often as the browser is able to render changes (I.E. 60fps in most browsers).

function mouseDown(e) {
    e.preventDefault(); // cancel default behavior
    isMouseDown     = true;
    startPosition   = e.pageX; // get position X
    currentPosition = getTranslateX(); // get current translateX value
}

var lastUpdateCall=null;
function mouseMove(e){
    if(isMouseDown){ //check if mousedown here, so there aren't any unnecessary animation frame requests when the user isn't dragging
        e.preventDefault(); // You probably only want to preventDefault when the user is actually dragging

        if(lastUpdateCall) cancelAnimationFrame(lastUpdateCall); //if an animation frame was already requested after last repaint, cancel it in favour of the newer event

        lastUpdateCall=requestAnimationFrame(function(){ //save the requested frame so we can check next time if one was already requested
            distancePosition = (e.clientX - startPosition) + currentPosition; // Do the distance calculation inside the animation frame request also, so the browser doesn't have to do it more often than necessary 
            update(); //all the function that handles the request
            lastUpdateCall=null; // Since this frame didn't get cancelled, the lastUpdateCall should be reset so new frames can be called. 
        });
    }
}

function update(){
    el.style.transform = "translateX(" + distancePosition + "px)";// move it!
}

You could also just not call requestAnimationFrame again if lastUpdateCall isn't null, but that would mean you'd have to calculate the distance outside the animation frame call every time the event fires, or the animation would lag behind your mouse by up to 20ms. Either method is fine, I suppose.

like image 88
Okku Avatar answered Oct 18 '22 09:10

Okku


The problem is that you are using requestAnimationFrame() in the mouseDown event listener. You should do all you updates in the mouseMove event listener because you want to update your display when the mouse moves not when mouse clicks. Accordingly you should update all your variables under the isMouseDown conditional in the update function. i would suggest correcting the code as follows.

HTML

<div id="drag">this is draggable</div>

CSS

#drag {
width:100px;
height:50px;
background-color:red;
transform:translateX(0);
}

JS

var el               = drag,
    startPosition    = 0, // start position mousedown event
    currentPosition  = 0, // count current translateX value
    distancePosition = 0, // count distance between "down" & "move" event
    isMouseDown      = false, // check if mouse is down or not
    needForRAF       = true;  // to prevent redundant rAF calls

function mouseDown(e) {
  e.preventDefault(); // reset default behavior
  isMouseDown     = true;
  currentPosition = getTranslateX(); // get current translateX value
  startPosition   = e.clientX; // get position X
}    

function mouseMove(e) {
    e.preventDefault();
  distancePosition = (e.clientX - startPosition) + currentPosition; // count it!  
  if (needForRAF && isMouseDown) {
    needForRAF = false;            // no need to call rAF up until next frame
    requestAnimationFrame(update); // request 60fps animation
  }; 
}

function mouseUp(e) {
  e.preventDefault();
  isMouseDown = false; // reset mouse is down boolean
}

function getTranslateX() {
  var translateX = parseInt(getComputedStyle(el, null).getPropertyValue("transform").split(",")[4]);
  return translateX; // get translateX value
}

function update() {
  needForRAF = true; // rAF now consumes the movement instruction so a new one can come
  el.style.transform = "translateX(" + distancePosition + "px)";// move it!
}

el.addEventListener("mousedown", mouseDown);
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);

check it up here

like image 35
Redu Avatar answered Oct 18 '22 07:10

Redu