Замена setTimeout() на 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())? :)

Я использую холсты HTML5 ($presentation и $fixation), которые рисуются во время generateTrial(current_trial).

Спасибо за помощь!

С уважением, Мартин Констант.


person Martin Constant    schedule 14.05.2020    source источник
comment
можете ли вы сделать минимальный воспроизводимый пример (фрагмент/скрипка или что-то еще), чтобы мы могли понять, чего вы пытаетесь достичь?   -  person Mhmdrz_A    schedule 14.05.2020


Ответы (1)


setTimeout действительно не синхронизируется с частотой обновления кадра, к нему даже будет применено некоторое регулирование, и браузер может отложить его, если решит, что другая задача важнее (например, они могут предпочесть запустить событие пользовательского интерфейса, если это происходит точно в то же время, когда setTimeout должен был разрешиться, и вызов его в рекурсивном цикле всегда будет накапливать некоторое время дрейфа.

Поэтому setTimeout не обеспечивает плавной анимации визуального контента.

С другой стороны, requestAnimationFrame запланирует запуск обратного вызова в следующем кадре рисования, как правило, синхронно с частотой обновления экрана.

requestAnimationFrame — идеальный инструмент для плавной анимации визуального контента.

Но здесь вы не плавно анимируете визуальный контент.

Частота обновления экрана, о которой мы говорим, на подавляющем большинстве устройств составляет 60 Гц, то есть 16,67 мс на кадр.
Ваши тайм-ауты установлены на 995 мс, 195 мс и 495 мс. Наименьший интервал там (195мс) примерно соответствует частоте 12Гц, наибольший почти 1Гц.

То, что вы делаете, — это планирование задач, и для этого лучше всего подходит 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 );

person Kaiido    schedule 15.05.2020