Javascript requestAnimationFrame spinner

时间:2015-08-14 13:05:04

标签: javascript animation

在研究了创建轻量级和灵活旋转器的所有可能方法之后,我最终使用了requestAnimationFrame,这非常棒。它基本上与CSS3 animation做同样的事情:执行计算并将结果移交给浏览器,以便将重绘与屏幕重绘同步(通常为60fps)。虽然CSS3 transitionanimation适合非常基本的使用,因为只有transitionend事件在某些情况下可能无法触发,requestAnimationFrame提供完全控制权您可以与屏幕重绘同步完美地执行多个复杂计算。

在HTML5工作者中执行此代码是否有意义?

CSS

i.spinner {position:relative;display:inline-block;margin:20px}
i.bar {display:block;position:absolute;top:0;left:50%;height:inherit}
i.bar i {display:block;width:100%;height:29%;background:#000}
i.bar:nth-child(2) {transform:rotate(45deg);-webkit-Transform:rotate(45deg);-moz-Transform:rotate(45deg);-ms-Transform:rotate(45deg)}
i.bar:nth-child(3) {transform:rotate(90deg);-webkit-Transform:rotate(90deg);-moz-Transform:rotate(90deg);-ms-Transform:rotate(90deg)}
i.bar:nth-child(4) {transform:rotate(135deg);-webkit-Transform:rotate(135deg);-moz-Transform:rotate(135deg);-ms-Transform:rotate(135deg)}
i.bar:nth-child(5) {transform:rotate(180deg);-webkit-Transform:rotate(180deg);-moz-Transform:rotate(180deg);-ms-Transform:rotate(180deg)}
i.bar:nth-child(6) {transform:rotate(225deg);-webkit-Transform:rotate(225deg);-moz-Transform:rotate(225deg);-ms-Transform:rotate(225deg)}
i.bar:nth-child(7) {transform:rotate(270deg);-webkit-Transform:rotate(270deg);-moz-Transform:rotate(270deg);-ms-Transform:rotate(270deg)}
i.bar:nth-child(8) {transform:rotate(315deg);-webkit-Transform:rotate(315deg);-moz-Transform:rotate(315deg);-ms-Transform:rotate(315deg)}

JS

function buildspinner(size, invert) {
  var color = '#000',
    spinner = document.createElement('i'),
    bar = document.createElement('i'),
    hand = document.createElement('i'),
    opacitymap = [0.8, 0.2, 0.2, 0.2, 0.2, 0.5, 0.6, 0.7],
    nodemap = [];
  if (invert) {color = '#fff'};
  spinner.className = 'spinner';
  spinner.style.cssText = 'width:' + size + 'px;height:' + size + 'px';
  bar.className = 'bar';
  bar.style.cssText = 'width:' + (size / 9) + 'px;height:' + size + 'px;margin-left:' + (-size / 18) + 'px';
  hand.style.cssText = 'border-radius:' + size + 'px;background:' + color;
  bar.appendChild(hand);
  for (var j = 0; j < 8; j++) {
    var clone = bar.cloneNode(true);
    clone.style.opacity = opacitymap[j];
    spinner.appendChild(clone);
    nodemap.push(clone)
  }
  document.body.appendChild(spinner);
  requestAnimationFrame(function(timestamp) {animatespinner(timestamp, timestamp, 125, opacitymap, nodemap, 0)})
}

function animatespinner(starttime, timestamp, duration, opacitymap, nodemap, counter) {
  var progress = (timestamp - starttime) / duration;
  counter++;
  if (counter % 3 == 0) {
    for (var j = 0; j < 8; j++) {
      var next = j - 1;
      if (next < 0) {
        next = 7
      };
      nodemap[j].style.opacity = (opacitymap[j] + (opacitymap[next] - opacitymap[j]) * progress)
    }
  }
  if (progress < 1) {
    requestAnimationFrame(function(timestamp) {animatespinner(starttime, timestamp, 125, opacitymap, nodemap, counter)})
  } else {
    var rotatearray = opacitymap.pop();
    opacitymap.unshift(rotatearray);
    requestAnimationFrame(function(timestamp) {animatespinner(timestamp, timestamp, 125, opacitymap, nodemap, 0)})
  }
}

counter变量用于限制。您希望动画流畅,但您希望保持较低的CPU使用率。在这个例子中,我们每隔3帧而不是每帧更改不透明度,大大降低了CPU开销,而不会对平滑度产生明显影响。 (Quadcore 3GHz处理器的CPU使用率从12%降至5%)。

由于CSS3 animation依赖于keyframes,因此您必须为每个微调器手创建一个单独的关键帧,从而导致计算过多。使用CSS3动画构建的相同微调器导致30%的CPU使用率。

Demo

2 个答案:

答案 0 :(得分:2)

requestAnimationFrame的要点是浏览器有效地调用它尽可能接近60fps,或者浏览器的动画引擎的帧速率,因此你不应该在requestAnimationFrame回调中工作,这需要花费比帧时间更长的时间。请记住,在浏览器中,javascript的执行发生得非常快......需要非常复杂的javascript计算才能实际花费比执行帧时间更长的时间。您遇到的主要问题是屏幕上的布局,绘画和重绘元素。为此,网络工作者不会帮助你。一个网络工作者只会帮助你,如果你有一个非常繁重的javascript需要比一个帧时间执行更长的时间。

这也非常容易分析...您可以查看Chrome的时间线工具,了解您的javascript函数执行的时间。它的最大可能性仅为1ms,如果你的动画运行速度低于60fps,那么因为布局和重新绘制所花费的时间比帧时间内剩余的16.7ms要长,但是这是浏览器布局引擎本身的问题,而不是你可以通过网络工作者卸载的东西。

答案 1 :(得分:2)

此代码显示浏览器向工作人员发送和接收消息所需的时间。在我的机器上大约需要3ms。如果你想达到60fps,你需要将JS的每个帧保持在10ms以下(记住浏览器仍然需要为每个帧设置样式,布局,绘制和合成)。

var myWorker, 
    send = document.querySelector('.send'),
    receive = document.querySelector('.receive'),
    time = document.querySelector('.time'),
    start, end;

var sendMessage = function () {
  start = performance.now();  
  myWorker.postMessage('My message');
  console.log('Sending message to worker ' + start);
};

var receiveMessage = function(event) {
  end = performance.now();
  time.textContent = (end - start) + 'ms';
  receive.textContent = event.data; 
  console.log('Message received from worker ' + end);
};

var workerFunction = function(event) { 
  self.postMessage('Worker response: ' + event.data); 
};

var createWorker = function () {
  if (window.Worker && window.Blob && window.URL) {
    var workerContent = "self.onmessage = " + workerFunction.toString();
    var blob = new Blob([workerContent], {type: 'application/javascript'}); 
    myWorker = new Worker(URL.createObjectURL(blob));
    myWorker.onmessage = receiveMessage;
  }
};

createWorker();

send.addEventListener('click', sendMessage);
<button class="send">Send</button>
<p class="receive"></p>
<p class="time"></p>