如何与setInterval一起使用requestAnimationFrame?

时间:2018-09-11 07:09:07

标签: javascript html css animation

请参见以下代码段。

单击按钮即可创建一个加载器。但是动画不流畅。

我最近阅读了有关requestAnimationFrame函数的信息,该函数可以完成此工作。但是,由于无法在setInterval函数中指定时间,因此如何使用它完全替换requestAnimationFrame

它可以与setInterval结合使用吗?

let idx = 1;
const timetoEnd = 5000;

function ProgressBar(width){
  this.width = width || 0;
  this.id = `pBar-${idx++}`;
  this.create = () => {
    let pBar = document.createElement('div');
    pBar.id = this.id;
    pBar.className = `p-bar`;
    pBar.innerHTML = `<div class="loader"></div>`;
    return pBar;
  };
  this.animator = () => {
      let element = document.querySelector(`#${(this.id)} div`);
      if(this.width < 100){
        this.width++;
        element.style.width = `${this.width}%`;
      } else {
        clearInterval(this.interval);
      }
  };
  this.animate = () => {
    this.interval = setInterval(this.animator, timetoEnd/100);
  }
}

function addLoader (){
  let bar1 = new ProgressBar(40);
  let container = document.querySelector("#container");
  container.appendChild(bar1.create());
  bar1.animate();
}
.p-bar{
  width: 400px;
  height: 20px;
  background: 1px solid #ccc;
  margin: 10px;
  overflow:hidden;
  border-radius: 4px;
}
.p-bar .loader{
  width: 0;
  background: #1565C0;
  height: 100%;
}
<input type="button" value="Add loader" onclick="addLoader()" />
<div id="container"></div>

1 个答案:

答案 0 :(得分:1)

您是对的,requestAnimationFrame是在制作动画时避免UI阻塞的推荐方法。

您可以记住开始时的绝对开始时间,而不是尝试在每个帧中都这样做。然后,只需根据开始时间和当前时间之间的增量时间计算宽度即可。

此外,document.querySelector被认为是相对“繁重”的操作,因此我添加了this.element以避免在每一帧都这样做。

以下是计算新宽度的方法:((100 - this.startWidth) / timetoEnd) * deltaT + this.startWidth

  1. 100 - this.startWidth是我们必须设置动画的总宽度
  2. (100 - this.startWidth) / timetoEnd是每秒必须增加多少宽度(1)
  3. ((100 - this.startWidth) / timetoEnd) * deltaT是我们必须增加(1)的宽度
  4. 我们只需要将整个this.startWidth px移至具有框架的宽度

还请注意,其中一些计算是恒定的,不必在每个帧上都进行计算,这是我在练习中留下的:)

这是您稍作修改的代码:

let idx = 1;
const timetoEnd = 5000;

function ProgressBar(startWidth){
  this.startWidth = startWidth || 0;
  this.id = `pBar-${idx++}`;
  this.create = () => {
    let pBar = document.createElement('div');
    pBar.id = this.id;
    pBar.className = `p-bar`;
    pBar.innerHTML = `<div class="loader"></div>`;
    return pBar;
  };
  this.animator = () => {
      const deltaT = Math.min(new Date().getTime() - this.start, timetoEnd);
      if(deltaT < timetoEnd){
        const width = ((100 - this.startWidth) / timetoEnd) * deltaT + this.startWidth;
        this.element.style.width = `${width}%`;
        requestAnimationFrame(this.animator.bind(this))
      }
  };
  this.animate = () => {
    this.element = document.querySelector(`#${(this.id)} div`);
    this.start = new Date().getTime();
    this.animator();
  }
}

function addLoader (){
  let bar1 = new ProgressBar(40);
  let container = document.querySelector("#container");
  container.appendChild(bar1.create());
  bar1.animate();
}
.p-bar{
  width: 400px;
  height: 20px;
  background: 1px solid #ccc;
  margin: 10px;
  overflow:hidden;
  border-radius: 4px;
}
.p-bar .loader{
  width: 0;
  background: #1565C0;
  height: 100%;
}
<input type="button" value="Add loader" onclick="addLoader()" />
<div id="container"></div>