Javascript move 元素与鼠标移动事件 60 FPS 请求动画帧

Javascript move element with mousemove event 60 FPS requestAnimationFrame

本文关键字:FPS 请求 动画 事件 移动 move 元素 鼠标 Javascript      更新时间:2023-09-26

那里! 我有一个问题#drag让元素顺利移动。

我看这篇文章:http://www.html5rocks.com/en/tutorials/speed/animations/#debouncing-mouse-events

它说:"当移动元素时mousemove事件的问题是鼠标移动事件触发太多

所以,我尝试使用他们的方法:使用 requestAnimationFrame + boolean checking .

看看这个真人小提琴: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);

这是正确的伴奏方式吗?

我的代码有什么问题?

谢谢

问题是您在mouseDown事件侦听器中使用requestAnimationFrame()。您应该在mouseMove事件侦听器中执行所有更新,因为您希望在鼠标移动时更新显示,而不是在鼠标单击时更新显示。因此,您应该在 update 函数中isMouseDown条件下更新所有变量。我建议按如下方式更正代码。

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);
#drag {
  width: 100px;
  height: 50px;
  background-color: red;
  transform: translateX(0);
}
<div id="drag">this is draggable</div>

在这里查看。

您的代码应该已经可以正常工作了。但是,这是另一种方法:

您需要确保每帧只允许一个requestAnimationFrame调用通过,否则update()将在下一个repaint被多次调用,这可能会导致延迟并降低您的 fps。为此,您需要保存请求的帧,并在每个mousemove事件上检查是否已有一个帧对齐。如果有,您需要使用 cancelAnimationFrame 取消它并提出新请求。这种方式update()仅在浏览器能够呈现更改的频率(即大多数浏览器中为 60fps)时才调用。

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

如果没有null,您也可以不再调用requestAnimationFrame lastUpdateCall但这意味着您必须在每次event触发时计算动画帧调用之外的距离,否则动画将落后于鼠标长达 20 毫秒。 我想这两种方法都可以。