简体   繁体   English

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

[英]requestAnimationFrame seems to update variables after cancelAnimationFrame is called

I'm trying to animate a camera rotation using MapBoxGL, while providing the option to pause the rotation and restart the rotation with a checkbox callback. 我正在尝试使用MapBoxGL为相机旋转设置动画,同时提供选项以暂停旋转并使用复选框回调重新启动旋转。 The 'pause'/'stop' rotation works fine, but the 'restart' seems to pick up the animation where the it should have been if it was never paused, as opposed to picking up where the animation stopped. “暂停” /“停止”旋转效果很好,但“重新启动”似乎是在从未暂停的情况下将动画拾取到应该在的位置,而不是在动画停止的位置进行拾取。

var animation;

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

When the map loads, the animation is called with: 加载地图时,将通过以下方式调用动画:

animation = rotateCamera(0);

The callback looks like this: 回调看起来像这样:

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

If I console.log the timestamp var inside the rotateCamera function, I can see that despite the cancelAnimationFrame call, it continues to be incremented. 如果我在console.logrotateCamera函数中使用timestamp var,则可以看到尽管执行cancelAnimationFrame调用,但它仍在继续递增。 I have tried declaring animation to be undefined upon a restart, and that doesn't seem to work either. 我尝试过在重新启动时声明animation是未定义的,而且这似乎也不起作用。 I'm stumped! 我很沮丧! Thanks for your help. 谢谢你的帮助。

The timestamp passed to the callback of requestAnimationFrame is an DOMHighResTimestamp, similar to the one returned by performance.now() . 传递给requestAnimationFrame回调的时间戳是DOMHighResTimestamp,类似于performance.now()返回的时间戳。 This timestamp indicates the number of milliseconds that elapsed since the beginning of the current document's lifetime (well it can be a bit more complicated ) when the callbacks execution started. 此时间戳表示自回调执行开始以来,当前文档生命周期开始以来经过的毫秒数(可能会更复杂 )。

So even when no requestAnimationFrame loop is running, this timestamp indeed increments, just like Date.now() also does. 因此,即使没有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> 

In your code, you will probably want to only keep the time that elapsed since last frame. 在您的代码中,您可能只想保留自上一帧以来经过的时间。 To do so, you can simply save timestamp in a variable that is globally available, and then in your callback do 为此,您只需将timestamp保存在全局可用的变量中,然后在回调中执行

var elapsed = timestamp - last_frame;
last_frame = timestamp;

And remember to also take care of the resume case, where timestamp will be undefined and elapsed should be reset. 并且请记住还要注意恢复情况,在该情况下, timestamp将是不确定的,并且应重设elapsed



Now, I'd like to point out that your description of the problem could also indicate an other problem entirely: you could have more than a single loop running simultaneously. 现在,我想指出的是,您对问题的描述也可能完全表明另一个问题:您可能同时运行多个循环。

Since you are using a single animation variable to hold the frame_id (used by cancelAnimationFrame), if you do call rotateCamera while a loop is already running, the first frame_ids will get lost, and their rAF loop will indeed continue. 由于您使用的是单个animation变量来保存frame_id(由cancelAnimationFrame使用),如果在循环已经运行的情况下调用rotateCamera ,则第一个frame_id将丢失,并且它们的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> 

I think it is possible in your code, since input[name='camerarotation-selection'] could change multiple times, when input[name='selection'] had not chnaged, or even since input[name='camerarotation-selection'] could be multiple elements. 我认为这在您的代码中是可行的,因为input[name='camerarotation-selection']可能会多次更改,当input[name='selection']尚未更改时,甚至是input[name='camerarotation-selection']可以是多个元素。

To avoid that, you could keep a semaphore variable allowing you to know if the loop is running or not, and to only start it when it's not. 为了避免这种情况,您可以保留一个信号量变量,让您知道循环是否正在运行,并仅在循环未运行时才启动。 Or you could even get rid entirely of cancelAnimationFrame by using only one semaphore, and exiting early in the rAF callback: 或者,您甚至可以仅使用一个信号量就完全摆脱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> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM