2016-03-19 11 views
2

dort! Ich habe ein Problem, #drag Element reibungslos zu bekommen.Javascript Element mit Mousemove-Ereignis verschieben 60 FPS requestAnimationFrame

Ich betrachte diesen Artikel: http://www.html5rocks.com/en/tutorials/speed/animations/#debouncing-mouse-events

gesagt, dass: „das Problem mit mousemove Ereignis, wenn sich bewegende Element mousemove- Ereignis war zu viel gefeuert

so, ich versuche, ihre Methode zu verwenden: using requestAnimationFrame + boolean checking

Blick auf diese Geige für 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); 

ist dies der richtige Weg, es zu accompolished?

Was ist falsch an meinem Code?

dank

+0

yep, was ist los mit Ihrem Code? – Kaiido

+0

danke Kaiido für die kommentierung. Ja wirklich? Ich fühle mich wie, es ist nicht wirklich glatt? –

+0

Auf meinem FF ist es perfekt glatt, ein bisschen blinkend in Chrom, das stimmt, aber sicher wegen dieser roten Farbe. – Kaiido

Antwort

4

Das Problem ist, dass Sie requestAnimationFrame() im mouseDown Ereignis-Listener verwenden. Sie sollten alle Aktualisierungen durchführen, die Sie im Ereignis-Listener vornehmen, da Sie Ihre Anzeige aktualisieren möchten, wenn sich die Maus nicht bewegt, wenn Sie mit der Maus klicken. Dementsprechend sollten Sie alle Ihre Variablen unter der isMouseDown Bedingung in der update Funktion aktualisieren. Ich würde vorschlagen, den Code wie folgt zu korrigieren.

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); 

es prüfen here

+0

Warum würden Sie den Animationsframe rekursiv in upadate() anfordern, wenn mouseMove() ihn auch aufruft? Und müsste die vorherige Anfrage nicht abgebrochen werden, wenn es mehr als ein mouseMove-Event pro Frame gibt? –

+0

@Ilpo Oksanen Ja, Sie scheinen recht zu haben. In diesem speziellen Fall ist die rekursive Pseudozuführung des requestAnimationFrame redundant. Es ist ein Überblick über mein Residuum von meinem Standardansatz, den Bildschirm zu aktualisieren. Vielen Dank für Ihre Hinweise. Ich werde das entsprechend korrigieren. – Redu

+2

Sie stapeln immer noch viele Aufrufe an requestAnimationFrame, die alle zur gleichen Zeit ausgeführt werden. Verwenden Sie einen booleschen Wert, um zu prüfen, ob Sie bereits ein Ereignis in diesem Frame behandelt haben, erhöhen Sie es, bevor Sie rAF aufrufen, und legen Sie es im rAF-Callback auf false fest. Wenn es bereits auf true gesetzt ist, rufe rAF nicht an. – Kaiido

1

Ihr Code sollte schon gut funktionieren. Aber hier ist eine andere Art und Weise, es zu tun:

Sie müssen sicherstellen, dass Sie nur einen requestAnimationFrame Anruf durch pro Frame gehen lassen, sonst update() wird mehrmals am nächsten repaint genannt werden, die Verzögerung und Abnahme verursachen können Ihre fps. Um dies zu tun, möchten Sie den angeforderten Rahmen speichern und bei jedem Ereignis mousemove prüfen, ob bereits ein Rahmen aufgereiht ist. Wenn dies der Fall ist, sollten Sie cancelAnimationFrame verwenden, um den Vorgang abzubrechen und eine neue Anfrage zu stellen. Auf diese Weise wird update() nur so oft aufgerufen, wie der Browser Änderungen vornehmen kann (I.E. 60 fps in den meisten Browsern).

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! 
} 

Sie könnten auch nicht nur requestAnimationFrame wieder anrufen, wenn lastUpdateCall nicht null ist, aber das würde bedeuten, Sie den Abstand außerhalb des Animationsrahmen Anruf berechnen müssten jedes Mal die event Feuer, oder die Animation würde hinken hinter Ihrer Maus um bis zu 20ms. Beide Methoden sind in Ordnung, nehme ich an.

Verwandte Themen