requestAnimationFrame 似乎在调用 cancelAnimationFrame 后更新变量

requestAnimationFrame seems to update variables after cancelAnimationFrame is called

我正在尝试使用 MapBoxGL 为相机旋转设置动画,同时提供暂停旋转和使用复选框回调重新启动旋转的选项。 'pause'/'stop' 旋转工作正常,但 'restart' 似乎在动画从未暂停的地方拾取动画,而不是在动画停止的地方拾取动画.

var animation;

function rotateCamera(timestamp) {
    map.rotateTo((timestamp / 600) % 360, {duration: 0});
    animation = requestAnimationFrame(rotateCamera);
}

加载地图时,调用动画:

animation = rotateCamera(0);

回调看起来像这样:

d3.selectAll("input[name='camerarotation-selection']").on("change", function() {
    if (d3.select("input[name='selection']").property("checked")) {
        rotateCamera(map.getBearing());
    } else {
        cancelAnimationFrame(animation);
    }
});

如果我在 rotateCamera 函数中 console.log 时间戳 var,我可以看到尽管有 cancelAnimationFrame 调用,它仍在继续递增。我曾尝试在重新启动时声明 animation 未定义,但这似乎也不起作用。我难住了!感谢您的帮助。

传递给 requestAnimationFrame 回调的时间戳是一个 DOMHighResTimestamp,类似于 performance.now() 返回的时间戳。 此时间戳指示回调执行开始时自当前文档生命周期开始以来经过的毫秒数(也可以是 a bit more complicated)。

所以即使没有 requestAnimationFrame 循环 运行,这个时间戳确实会增加,就像 Date.now() 一样。

let animation = 0;
inp.onchange = e => {
  if (inp.checked) start();
  else {
    cancelAnimationFrame(animation);
  }
};

function start(timestamp) {
  _log.textContent = timestamp;
  animation = requestAnimationFrame(start);
}
<input type="checkbox" id="inp">
<pre id="_log"></pre>

在您的代码中,您可能只想保留自上一帧以来经过的时间。为此,您只需将 timestamp 保存在全局可用的变量中,然后在您的回调中执行

var elapsed = timestamp - last_frame;
last_frame = timestamp;

并记住还要注意简历的情况,其中 timestamp 将是未定义的并且 elapsed 应该被重置。



现在,我想指出您对问题的描述也可能完全表明另一个问题:您可能同时有多个循环 运行。

由于您使用单个 animation 变量来保存 frame_id(由 cancelAnimationFrame 使用),如果您调用 rotateCamera 而循环已经 运行 , 前 frame_ids 会迷路,他们的 rAF 循环确实会继续。

let common_id = 0; // OP's `animation` variable
let first_id = 0;
let second_id = 0;

function loop1(timestamp) {
  common_id = first_id = requestAnimationFrame(loop1);
  log_1.textContent = "loop 1: " + timestamp;
}

function loop2(timestamp) {
  common_id = second_id = requestAnimationFrame(loop2);
  log_2.textContent = "loop 2: " + timestamp;
}

btn.onclick = e => {
  console.log("first loop's id", first_id);
  console.log("second loop's id", second_id);
  console.log('clearing common_id', common_id);
  cancelAnimationFrame(common_id);
}

loop1();
loop2();
<button id="btn">stop the loop</button>

<pre id="log_1"></pre>
<pre id="log_2"></pre>

我认为这在您的代码中是可能的,因为 input[name='camerarotation-selection'] 可以更改多次,而 input[name='selection'] 没有更改,甚至因为 input[name='camerarotation-selection'] 可能是多个元素。

为了避免这种情况,您可以保留一个信号量变量,让您知道循环是否为 运行,并且仅在不是时启动它。 或者你甚至可以通过只使用一个信号量完全摆脱 cancelAnimationFrame,并在 rAF 回调中提前退出:

let stopped = true;

function loop(timestamp) {
  if (stopped) return; // exit early
  requestAnimationFrame(loop);
  log.textContent = timestamp;
}
// you can click it several times
btn_start.onclick = e => {
  if (stopped === true) { // only if not running yet
    stopped = false;
    requestAnimationFrame(loop);
  }
}
btn_stop.onclick = e => {
  stopped = true; // deal only with the semaphore
};

btn_switch.onclick = e => {
  stopped = !stopped;
  if (stopped === false) { // we were paused
    // start again
    requestAnimationFrame(loop);
  }
}
<button id="btn_start">start the loop</button>
<button id="btn_stop">stop the loop</button>
<button id="btn_switch">switch the loop</button>

<pre id="log"></pre>