Как исправить точность setTimeout / requestAnimationFrame - PullRequest
0 голосов
/ 20 июня 2019

Для демонстрации проблемы, пожалуйста, смотрите здесь:

https://gyazo.com/06e423d07afecfa2fbdb06a6da77f66a

Я получаю прыжковое поведение при приостановке уведомления.Это также зависит от того, как долго мышь остается в уведомлении, и насколько близок прогресс до конца.

Я пробовал так много вещей, я больше не уверен, действительно ли проблема в setTimeout.

Это похоже на то, как если бы с момента вычисления this.timerFinishesAt до первой итерации requestAnimationFrame скачок прогресса происходил из-за ожидания времени процессора?Но опять же, почему на это влияют время зависания и прогресс.

Как смягчить поведение при прыжке?

Я прочитал / попытался внедрить исправление из следующих ресурсов среди поисковна другие вопросы stackoverflow:

https://gist.github.com/tanepiper/4215634

Как создать точный таймер в javascript?

В чем причина JavaScript setTimeoutтак неточно?

https://www.sitepoint.com/creating-accurate-timers-in-javascript/

https://codepen.io/sayes2x/embed/GYdLqL?default-tabs=js%2Cresult&height=600&host=https%3A%2F%2Fcodepen.io&referrer=https%3A%2F%2Fmedium.com%2Fmedia%2Fb90251c55fe9ac7717ae8451081f6366%3FpostId%3D255f3f5cf50c&slug-hash=GYdLqL

https://github.com/Falc/Tock.js/tree/master

https://github.com/philipyoungg/timer

https://github.com/Aaronik/accurate_timer

https://github.com/husa/timer.js

timerStart(){
   // new future date = future date + elapsed time since pausing
   this.timerFinishesAt = new Date( this.timerFinishesAt.getTime() + (Date.now() - this.timerPausedAt.getTime()) );
   // set new timeout
   this.timerId = window.setTimeout(this.toggleVisibility, (this.timerFinishesAt.getTime() - Date.now()));
   // animation start
   this.progressId = requestAnimationFrame(this.progressBar);
},
timerPause(){
   // stop notification from closing
   window.clearTimeout(this.timerId);
   // set to null so animation won't stay in a loop
   this.timerId = null;
   // stop loader animation from progressing
   cancelAnimationFrame(this.progressId);
   this.progressId = null;

   this.timerPausedAt = new Date();
},
progressBar(){
   if (this.progress < 100) {
     let elapsed = Date.now() - this.timerStarted.getTime();
     let wholeTime = this.timerFinishesAt.getTime() - this.timerStarted.getTime();
     this.progress = Math.ceil((elapsed / wholeTime) * 100);

     if (this.timerId) {
       this.progressId = requestAnimationFrame(this.progressBar);
     }

   } else {
     this.progressId = cancelAnimationFrame(this.progressId);
   }
}

Ответы [ 2 ]

0 голосов
/ 20 июня 2019

Когда вы вычисляете текущий прогресс вашего таймера, вы не учитываете время паузы.Отсюда и переходы: эта часть вашего кода знает только о startTime и currentTime, паузы на нее не влияют.

Чтобы обойти это, вы можете либо накопить все это время паузы в функции startTimer

class Timer {
  constructor() {
    this.progress = 0;
    this.totalPauseDuration = 0;
    const d = this.timerFinishesAt = new Date(Date.now() + 10000);
    this.timerStarted = new Date();
    this.timerPausedAt = new Date();
  }
  timerStart() {
    const pauseDuration = (Date.now() - this.timerPausedAt.getTime())

    this.totalPauseDuration += pauseDuration;

    // new future date = future date + elapsed time since pausing
    this.timerFinishesAt = new Date(this.timerFinishesAt.getTime() + pauseDuration);
    // set new timeout
    this.timerId = window.setTimeout(this.toggleVisibility.bind(this), (this.timerFinishesAt.getTime() - Date.now()));
    // animation start
    this.progressId = requestAnimationFrame(this.progressBar.bind(this));
  }
  timerPause() {
    // stop notification from closing
    window.clearTimeout(this.timerId);
    // set to null so animation won't stay in a loop
    this.timerId = null;
    // stop loader animation from progressing
    cancelAnimationFrame(this.progressId);
    this.progressId = null;

    this.timerPausedAt = new Date();
  }
  progressBar() {
    if (this.progress < 100) {
      let elapsed = (Date.now() - this.timerStarted.getTime()) - this.totalPauseDuration;
      let wholeTime = this.timerFinishesAt.getTime() - this.timerStarted.getTime();
      this.progress = Math.ceil((elapsed / wholeTime) * 100);
      
      log.textContent = this.progress;
      
      if (this.timerId) {
        this.progressId = requestAnimationFrame(this.progressBar.bind(this));
      }

    } else {
      this.progressId = cancelAnimationFrame(this.progressId);
    }
  }
  toggleVisibility() {
    console.log("done");
  }
};

const timer = new Timer();

btn.onclick = e => {
  if (timer.timerId) timer.timerPause();
  else timer.timerStart();
};
переключить

или обновить startTime, что представляется более надежным:

class Timer {
  constructor() {
    this.progress = 0;
    const d = this.timerFinishesAt = new Date(Date.now() + 10000);
    this.timerStarted = new Date();
    this.timerPausedAt = new Date();
  }
  timerStart() {
    const pauseDuration = (Date.now() - this.timerPausedAt.getTime())

    // update timerStarted
    this.timerStarted = new Date(this.timerStarted.getTime() + pauseDuration);

    // new future date = future date + elapsed time since pausing
    this.timerFinishesAt = new Date(this.timerFinishesAt.getTime() + pauseDuration);
    // set new timeout
    this.timerId = window.setTimeout(this.toggleVisibility.bind(this), (this.timerFinishesAt.getTime() - Date.now()));
    // animation start
    this.progressId = requestAnimationFrame(this.progressBar.bind(this));
  }
  timerPause() {
    // stop notification from closing
    window.clearTimeout(this.timerId);
    // set to null so animation won't stay in a loop
    this.timerId = null;
    // stop loader animation from progressing
    cancelAnimationFrame(this.progressId);
    this.progressId = null;

    this.timerPausedAt = new Date();
  }
  progressBar() {
    if (this.progress < 100) {
      let elapsed = Date.now() - this.timerStarted.getTime();
      let wholeTime = this.timerFinishesAt.getTime() - this.timerStarted.getTime();
      this.progress = Math.ceil((elapsed / wholeTime) * 100);
      
      log.textContent = this.progress;
      
      if (this.timerId) {
        this.progressId = requestAnimationFrame(this.progressBar.bind(this));
      }

    } else {
      this.progressId = cancelAnimationFrame(this.progressId);
    }
  }
  toggleVisibility() {
    console.log("done");
  }
};

const timer = new Timer();

btn.onclick = e => {
  if (timer.timerId) timer.timerPause();
  else timer.timerStart();
};
toggle

Что касается последнего разрыва, не видя, как этот код связан с вашим пользовательским интерфейсом, трудно сказать, чтослучается.

0 голосов
/ 20 июня 2019

Я думаю, что достаточно просто использовать SetInterval:

const progressBar = {
  MsgBox : document.querySelector('#Message'),
  Info   : document.querySelector('#Message h1'),
  barr   : document.querySelector('#Message progress'),
  interV : 0,
  DTime  : 0,
  D_Max  : 0,

  Init() {
    this.MsgBox.onmouseover=_=> {   // pause
      clearInterval( this.interV )
    }
    this.MsgBox.onmouseout=_=>{     // restart
      this._run()
    }
  },
  Start(t,txt)
  {
    this.DTime = this.D_Max = t * 1000
    this.barr.value = 0
    this.barr.max = this.D_Max
    this.Info.textContent = txt
    this._run()
  },
  _run()
  {
    let D_End = new Date(Date.now() + this.DTime )

    this.interV = setInterval(_=>{
      this.DTime = D_End - (new Date(Date.now()))

      if (this.DTime > 0) { this.barr.value = this.D_Max - this.DTime }
      else                { clearInterval( this.interV ); console.clear(); console.log( "finish" ) }      
    }, 100);
  }
}


progressBar.Init()

progressBar.Start(10, 'Hello!') // 10 seconds
#Message {
  box-sizing: border-box;
  display: block;
  float: right;
  width: 200px;
  height: 80px;
  background-color: darkslategrey;
  padding: 0 1em;
  color:#e4a8b4;
  cursor: pointer;
  margin-right:1.5em;
}
#Message h1 { margin: .3em 0 0 0}
#Message progress { height: .1em; margin: 0; width:100%; background-color:black; }
#Message progress::-moz-progress-bar,
#Message progress::-webkit-progress-value { background-color:greenyellow; }
<div id="Message">
  <progress value="50" max="100" ></progress>
  <h1> </h1>
</div>
...