首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >requestAnimationFrame困惑

requestAnimationFrame困惑
EN

Stack Overflow用户
提问于 2017-05-02 18:46:12
回答 2查看 810关注 0票数 2

我开始用这个方法学习requestAnimationFrame,但是我发现它有点难理解(至少对我来说是这样)。我知道setInterval/setTimeout是如何工作的,但是当我尝试输入一些简单的代码进行测试时:

代码语言:javascript
复制
function message(){console.log('hello');}
requestAnimationFrame(message);

这不会使控制台中出现hello一次的任何动画,但是当我在消息函数中调用requestAnmiationFrame(message)时,循环是无限的(因此它是工作的)。

但以另一种方式,我可以使用setInterval/setTimeout在任何地方调用这个消息函数,而不是在消息函数中调用。

那么,我可以理解requestAnimationFrame()方法必须在函数内部运行吗?或者我应该说,在使用requestAnimiationFrame()方法时,函数有一个默认的for循环,用于循环自身无穷大的时间?

这个问题可能很愚蠢,但真的希望有人能启发我。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-05-02 18:51:46

requestAnimationFrame实际上并不为您呈现任何东西,它只是将浏览器的呈现循环同步到您的代码上,因此您可以使用它在屏幕上以编程方式呈现动画。

它所做的是激发每次浏览器呈现框架时提供给它的回调函数。下面是一个很好的例子,说明了它的用途:制作一个javascript游戏

票数 1
EN

Stack Overflow用户

发布于 2018-09-13 16:13:29

rAF(请求动画帧)和timers (setTimeout / setInterval)有着巨大的区别。

两者都在创建运行异步的任务(您的回调),即浏览器对它们进行排队,并在特定时间点调用它们。

最大的区别在于它们何时得到执行和优先级。

我们需要了解谁在管理Que,在什么条件下可以运行回调。

我会尽量保持简单。

同步JavaScript

JavaScript是“单线程”,这意味着您不能在普通JavaScript中运行异步操作。

browser API提供了一些运行代码异步的方法:

  • timers
  • rAF
  • DOM事件
  • promises (这是一种不同的任务)
  • xhr
  • 等等。

当您使用这些方法之一时,实际上将一个函数引用传递给浏览器,以便(希望)在所需的时间点调用它。

事件循环

浏览器将根据您使用的API完成它的工作,并在任务Que中进行回调(除了承诺之外,这些都是微任务)。

最大的问题是什么时候将它传递回"javascript-land“到调用堆栈中?

这是事件循环的工作,它将检查两个条件:

  1. 调用堆栈是空的(意味着当前没有函数在“主线程”上运行)。
  2. 是否有任何代码可以在“主线程”上运行。

如果它满足这两个条件,它将从Que中提取下一个任务并将其推送到调用堆栈。

现在让我们以setTimeout为例。

鉴于这一守则:

代码语言:javascript
复制
setTimeout(() => console.log('timeout'), 0);
console.log('start');
console.log('end');

setTimout应该等待0ms,然后再记录timeout,所以我们应该认为日志顺序是:

  1. timeout
  2. start
  3. end

但实际上是:

  1. start
  2. end
  3. timeout

原因是我们将回调传递给浏览器,浏览器等待0ms,然后将其推送到任务que。

现在,事件循环的任务是检查上面提到的两个条件:

  1. 调用堆栈是空的(意味着当前没有函数在“主线程”上运行)。
  2. 是否有任何代码可以在“主线程”上运行。

这两个问题的答案都是否定的,因为我们仍然有两个console.log行要在主执行上下文上运行。

所以它等待,当我们完成日志时,它将回调从que传递到调用堆栈,然后记录timeout

大多数异步回调都是这样工作的。

注意到promises是不同的,它们是微任务,具有比常规回调任务更高的优先级。

好吧,这些话我还没回答你的问题。

但在回答之前还有一件事。

requestanimationFrame

是另一个允许您在浏览器即将运行呈现步骤之前运行内容的浏览器API:

  1. 样式计算
  2. 布局
  3. 实际印刷

因此,例如,如果您想更新元素在下一个帧上的left位置,这就是您要做的地方。

这是一个可能的实施:

代码语言:javascript
复制
requestAnimationFrame(moveBox)

如果您希望它继续在每个帧上移动,您应该递归地调用它:

代码语言:javascript
复制
function callback() {
    moveBox();
    requestAnimationFrame(callback);
}
callback(); 

那么为什么或何时使用rAF over timers呢?

代码语言:javascript
复制
setInterval(moveBox, 0)

这将比rAF实现运行得更快。其运行速度较快的原因是,在浏览器有机会重新绘制(处理呈现步骤)之前,回调将被多次调用,因此您将多次“更新”left位置,但浏览器只会重新绘制您传递的最后一个值。

例如,它将从0px跳转到15px,而不是一次增加一个像素。

基本上,你能做的是“猜”帧之间会传递多少时间。最常见的方法是计算“每秒60帧”,因为大多数屏幕都支持这一比率。

代码语言:javascript
复制
setInterval(moveBox2, 1000 / 60)  

但是,即使这样也不会顺利或一致,因为它不是真正的60 fps,最重要的是,也许当前的设备和屏幕刷新的速度不像你想象的那样。

因此,处理DOM动画、批处理更新和度量内容的最佳API是requestAnimationFrame

运行示例

我举了一个用rAFsetInterval的2种解决方案移动一个盒子的小例子,希望它能有所帮助。

代码语言:javascript
复制
const showBox2 = location.search.includes('box2');
const showBox3 = location.search.includes('box3');
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');

// move by 1 px
function move1px(el) {
  let left = 0;
  return function() {
    // infinite looping logic - if off screen start over
    if (document.documentElement.clientWidth < left) {
      left = 0;
    } else {
      left++;
    }
    // move left by 1 px
    el.style.left = left + 'px';
  }
}
// raf solution
const moveBox1 = move1px(box1);

function callback() {
  moveBox1();
  requestAnimationFrame(callback);
}
callback();

// setInterval solution
const moveBox2 = move1px(box2);
setInterval(moveBox2, 0);

// setInterval hack
const moveBox3 = move1px(box3);
setInterval(moveBox3, 1000 / 60);
代码语言:javascript
复制
body {
  overflow: hidden;
}

#root {
  display: flex;
  flex-direction: column;
}

.box {
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  height: 30px;
  width: 150px;
  border: 1px solid #333;
  margin: 10px 0;
  color: #fff;
  font-size: 1em;
}

#box1 {
  background-color: slateblue;
}

#box2 {
  background-color: brown;
}

#box3 {
  background-color: green;
}
代码语言:javascript
复制
<div id="root">
  <div class="box" id="box3">setInterval hack</div>
  <div class="box" id="box1">rAF</div>
  <div class="box" id="box2">setInterval</div>
</div>

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43745088

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档