用 requestAnimationFrame() 替换 setTimeout()

Replacing setTimeout() with requestAnimationFrame()

我是一名实验心理学博士生,由于 COVID-19,我们不得不将所有实验转到网上。我也不是很了解Javascript

问题是我们通常会在很短的时间内(例如 200 毫秒)呈现刺激,并且我们需要最少的可变性,因此我们通常与监视器刷新率同步。

我对 Javascript 的有限理解是 setTimeout() 与监视器帧无关(因此应显示 200 毫秒的刺激实际上可能在屏幕上显示的时间比此持续时间更长),并且requestAnimationFrame() 会更精确。然而,我花了最近几天试图了解如何使用 requestAnimationFrame() 而不是 setTimeout() 但无济于事,我找到的所有教程都是用于显示动画刺激。这是我现在用来处理实验流程的代码片段。

setTimeout(function() {
    generateTrial(current_trial);
    $fixation.show();
    setTimeout(function() {
        $fixation.toggle();
        $presentation.toggle();
        setTimeout(function() {
            $presentation.toggle();
            $fixation.toggle();
            setTimeout(function() {
                ShowContinuousReport(current_trial);
            }, 995);
        }, 195);
    }, 995);
}, 495);

您是否知道如何将所有这些讨厌的 setTimeout() 转换为 requestAnimationFrame()(或至少比 setTimeout() 更好的东西)? :)

我使用在 generateTrial(current_trial) 期间绘制的 HTML5 画布($presentation$fixation)。

感谢您的帮助!

此致, 马丁·康斯坦。

setTimeout 确实与帧刷新率不同步,它甚至会应用一些节流,如果浏览器认为其他任务更重要(例如,他们可能更愿意触发一个 UI 事件,如果它恰好发生在 setTimeout 应该解决的同一时间,并且在递归循环中调用它总是会累积一些 drift 时间.

所以 setTimeout 不能使视觉内容流畅地动画化。

另一方面,requestAnimationFrame会安排一个回调在下一个绘画帧触发,通常与屏幕刷新率同步。

requestAnimationFrame 是流畅地制作视觉内容动画的完美工具。

但是在这里你没有流畅地动画化视觉内容。

我们所说的屏幕刷新率在绝大多数设备上都是60Hz,即每帧16.67ms。
您的超时设置为 995ms、195ms 和 495ms。那里最小的间隔(195ms)大约对应于 12Hz 的频率,最大的几乎是 1Hz。

你做的是调度任务,为此,setTimeout是最好的。

如果你真的需要它在很长一段时间内尽可能精确运行,那么在你的循环中加入漂移校正逻辑:

记录开始时间,然后在每一步检查漂移量,并相应地调整下一个超时时间:

这是一个基本的例子,根据你的情况,但是在这么小的样本上可能很难得到漂移校正的用处,还是要注意漂移校正版本是如何减少漂移,而在未校正的情况下,它总是会加起来。

const delays = [ 495, 995, 195, 995 ];

setTimeout(() => {
console.log( 'testing with drift correction' );
const start_time = performance.now();
let expected_time = start_time;
setTimeout( () => {
  // do your things here
  const now = performance.now();
  expected_time += delays[ 0 ];
  const drift = now - expected_time;
  setTimeout( () => {
    const now = performance.now();
    expected_time += delays[ 1 ];
    const drift = now - expected_time;
    setTimeout( () => {
      const now = performance.now();
      expected_time += delays[ 2 ];
      const drift = now - expected_time;
      setTimeout( () => {
        const now = performance.now();
        expected_time += delays[ 3 ];
        const drift = now - expected_time;
        console.log( 'last step drift corrected:', drift );
      }, delays[ 3 ] - drift );
      console.log( 'third step drift corrected:', drift );
    }, delays[ 2 ] - drift );
    console.log( 'second step drift corrected:', drift );
  }, delays[ 1 ] - drift );
  console.log( 'first step drift corrected:', drift );
}, delays[ 0 ] );

}, 100 );

setTimeout( () => {

console.log( 'testing without drift correction' );
const start_time = performance.now();
let expected_time = start_time;

setTimeout( () => {
  // do your things here
  const now = performance.now();
  expected_time += delays[ 0 ];
  const drift = now - expected_time;
  setTimeout( () => {
    const now = performance.now();
    expected_time += delays[ 1 ];
    const drift = now - expected_time;
    setTimeout( () => {
      const now = performance.now();
      expected_time += delays[ 2 ];
      const drift = now - expected_time;
      setTimeout( () => {
        const now = performance.now();
        expected_time += delays[ 3 ];
        const drift = now - expected_time;
        console.log( 'last step drift not corrected:', drift );
      }, delays[ 3 ] );
      console.log( 'last step drift not corrected:', drift );
    }, delays[ 2 ] );
    console.log( 'last step drift not corrected:', drift );
  }, delays[ 1 ] );
  console.log( 'last step drift not corrected:', drift );
}, delays[ 0 ] );
}, 3000 );