JavaScript请求动画帧旋转器

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

在HTML5 worker中执行此代码是否有意义?

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,您必须为每个旋转手创建单独的关键帧,这会导致太多的计算。相同的Spinner使用CSS3 animation构建,结果CPU使用率达到30%。 演示

您在worker和主应用程序中具体会做什么?您是否考虑过数据来回传输的速度?也许您可以测试一下,测量性能并告诉我们。 - Shomz
@Shomz:我不熟悉工作者,因此才有这个问题。旋转器基本上是一个重复的进程,所以我在想如果旋转器在单独的线程中运行会如何影响CPU使用情况? - Sauvage
1
嗯,使用Web Worker可以很好地处理一些繁重的计算,并将结果传递给主应用程序。但由于大多数代码都在操作DOM,我认为使用Web Worker没有什么好处...不过让我们看看是否有人能给你一个更好的答案。 - Shomz
抱歉,我不确定… 你能强调使用Canvas技术的好处吗? - Sauvage
它可以避免您使用多个 DOM 元素(仅使用一个元素),并且可以给出相同的结果……可能还会有更好的性能。 - Shomz
显示剩余2条评论
2个回答

2
requestAnimationFrame 的目的是,在浏览器中以尽可能接近 60fps 或者浏览器动画引擎的帧率高效地调用,因此在 requestAnimationFrame 回调中不应执行超过一帧时间的工作。请记住,在浏览器中,JavaScript 的执行非常快... 实际上需要非常复杂的 JavaScript 计算才能超过一帧时间来执行。你将遇到的主要问题是布局、绘制屏幕元素和重绘。对于这些问题,Web Worker 并不能帮助你。只有当你有真正需要执行超过一帧时间的大量 JavaScript 时,Web Worker 才能帮助你。
这也很容易进行性能分析。您可以查看 Chrome 的时间轴工具,了解您的 JavaScript 函数执行时间。很有可能最多只有 1 毫秒,而如果您的动画运行速度低于 60fps,则是因为布局和重绘所需的时间超过了剩余的 16.7ms 帧时间,但这是由浏览器布局引擎本身处理的,并不是您可以通过 Web Worker 卸载的内容。

在Chrome中测试:fps=60,JavaScript执行平均值=0.214毫秒。 - Sauvage

2

这段代码展示了浏览器发送和接收消息到worker所需要的时间,对于我的机器来说大约需要3毫秒。如果想要达到60帧每秒的效果,你需要将JS的每一帧控制在10毫秒以内(请记住浏览器还需要为每一帧完成样式、布局、绘制和合成)。

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>


确实,将代码传递给/从工作线程需要超过1毫秒的时间,而非工作线程的示例平均需要0.214毫秒才能执行。 - Sauvage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接