web-dev-qa-db-ja.com

setTimeoutよりもJavaScriptタイマーを作成するより正確な方法はありますか?

いつも私を悩ませてきたのは、JavascriptのsetTimeout()メソッドがどれほど予測不能かということです。

私の経験では、タイマーは多くの状況で恐ろしく不正確です。不正確ということで、実際の遅延時間は250〜500ミリ秒程度増減しているように見えます。これは膨大な時間ではありませんが、UI要素を非表示/表示するために使用する場合、時間が目に見えて目立つ可能性があります。

setTimeout()が正確に(外部APIに頼ることなく)実行することを保証するために実行できるトリックはありますか、またはこれは失われた原因ですか?

66
Dan Herbert

setTimeout()が(外部APIに頼らずに)正確に実行されるようにするために実行できるトリックはありますか、またはこれが失われた原因ですか?

いやいや。 setTimeout()を使用して、完全に正確なタイマーに近いものを取得することはできません。そのためのブラウザーはセットアップされていません。 ただし、タイミングを計るためにもそれに依存する必要はありません。ほとんどのアニメーションライブラリは、数年前にこれを考え出しました。setTimeout()を使用してコールバックを設定しますが、_(new Date()).milliseconds_(または同等の)の値に基づいて実行する必要があるものを決定します。これにより、新しいブラウザでより信頼性の高いタイマーサポートを利用しながら、古いブラウザでも適切に動作できます。

また、あまりにも多くのタイマーの使用を避けることができます!これは重要です。各タイマーはコールバックです。各コールバックはJSコードを実行します。 JSコードの実行中、ブラウザイベント(他のコールバックを含む)は遅延またはドロップされます。コールバックが終了すると、追加のコールバックは、実行する機会を得るために他のブラウザーイベントと競合する必要があります。したがって、その間隔のすべての保留中のタスクを処理する1つのタイマーは、一致する間隔を持つ2つのタイマーよりも優れたパフォーマンスを発揮します。

概要:setTimeout()の使用を停止して「1タイマー/ 1タスク」設計を実装し、リアルタイムクロックを使用してUIアクションをスムーズにします。

72
Shog9

REF; http://www.sitepoint.com/creating-accurate-timers-in-javascript/

このサイトは私を大規模に救済しました。

システムクロックを使用して、タイマーの不正確さを補正できます。タイミング関数を一連のsetTimeout呼び出し(各インスタンスが次を呼び出す)として実行する場合、正確に保つために必要なことは、それがどれだけ不正確かを判断し、次の反復からその差を差し引くことです。

var start = new Date().getTime(),  
    time = 0,  
    elapsed = '0.0';  
function instance()  
{  
    time += 100;  
    elapsed = Math.floor(time / 100) / 10;  
    if(Math.round(elapsed) == elapsed) { elapsed += '.0'; }  
    document.title = elapsed;  
    var diff = (new Date().getTime() - start) - time;  
    window.setTimeout(instance, (100 - diff));  
}  
window.setTimeout(instance, 100);  

この方法により、ドリフトが最小限に抑えられ、不正確さが90%以上削減されます。

それは私の問題を修正し、それが役立つことを願っています

32
user1213320

少し前に似たような問題があり、requestAnimationFrameとperformance.now()を組み合わせて非常に効果的に機能するアプローチを思いつきました。

タイマーを小数点以下約12桁まで正確に作成できるようになりました。

    window.performance = window.performance || {};
    performance.now = (function() {
        return performance.now       ||
            performance.mozNow    ||
            performance.msNow     ||
            performance.oNow      ||
            performance.webkitNow ||
                function() {
                    //Doh! Crap browser!
                    return new Date().getTime(); 
                };
        })();

http://jsfiddle.net/CGWGreen/9pg9L/

10
Chris GW Green

shog9の答えは私が言うこととほぼ同じですが、UIアニメーション/イベントについては次のように追加します。

画面上にスライドし、下向きに展開し、その内容をフェードインすることになっているボックスがある場合、3つのイベントすべてを、次々に発火するようにタイミングを合わせて遅延させないでください-一度コールバックを使用してください最初のイベントはスライドして行われ、エキスパンダーを呼び出します。一度行われるとフェーダーを呼び出します。 jQueryは簡単に実行でき、他のライブラリも同様に実行できると確信しています。

4
matt lohkamp

特定の間隔で正確なコールバックを取得する必要がある場合、このGistが役立ちます。

https://Gist.github.com/1185904

4
manast

setTimeout()を使用してブラウザにすばやく譲る場合、UIスレッドは必要なタスクに追いつくことができます(タブの更新、または長時間実行スクリプトダイアログを表示しないなど)、 Efficient Script Yielding 、aka、setImmediate()と呼ばれる新しいAPIがあります。あなたのために。

setImmediate()setTimeout()と非常によく似た動作をしますが、ブラウザに他に何もすることがない場合はすぐに実行されます。 setTimeout(..., 16)またはsetTimeout(..., 4)またはsetTimeout(..., 0)を使用している多くの状況(つまり、ブラウザーで未処理のUIスレッドタスクを実行し、長時間実行スクリプトを表示しないようにする場合)ダイアログ)、setTimeout()setImmediate()に置き換えるだけで、2番目(ミリ秒)の引数を削除できます。

setImmediate()との違いは、基本的にyieldであるということです。ブラウザがUIスレッドで実行する(たとえば、タブを更新する)場合は、コールバックに戻る前に実行します。ただし、ブラウザがすでにすべての作業に追いついている場合、setImmediate()で指定されたコールバックは基本的に遅延なく実行されます。

残念ながら、他のブラウザベンダーからのいくつかの プッシュバック があるため、IE9 +でのみ現在サポートされています。

ただし、 good polyfill があります。これを使用し、他のブラウザがいつかそれを実装することを望む場合。

アニメーションにsetTimeout()を使用している場合requestAnimationFrame がコードの実行に最適です-モニターのリフレッシュレートと同期します。

遅いケイデンスでsetTimeout()を使用している場合、例えば300ミリ秒ごとに、user1213320が提案するものと同様のソリューションを使用できます。このソリューションでは、タイマーが実行した最後のタイムスタンプからの時間を監視し、遅延を補正します。 1つの改善点は、window.performance.now()の代わりに新しい High Resolution Time インターフェイス(別名Date.now())を使用して、現在のミリ秒よりも大きい解像度を取得できることです。時間。

3
NicJ

目標時間に「忍び寄る」必要があります。いくつかの試行錯誤が必要ですが、本質的には。

タイムアウトを設定して、必要な時間の約100ミリ秒前に完了します

タイムアウトハンドラー関数を次のようにします。

calculate_remaining_time
if remaining_time > 20ms // maybe as much as 50
  re-queue the handler for 10ms time
else
{
  while( remaining_time > 0 ) calculate_remaining_time;
  do_your_thing();
  re-queue the handler for 100ms before the next required time
}

ただし、whileループは他のプロセスによって中断される可能性があるため、まだ完全ではありません。

2
Noel Walters

Shog9の提案をデモする例を次に示します。これにより、jqueryプログレスバーが6秒にわたってスムーズにいっぱいになり、いっぱいになると別のページにリダイレクトされます。

var TOTAL_SEC = 6;
var FRAMES_PER_SEC = 60;
var percent = 0;
var startTime = new Date().getTime();

setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);

function updateProgress() {
    var currentTime = new Date().getTime();

    // 1000 to convert to milliseconds, and 100 to convert to percentage
    percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100;

    $("#progressbar").progressbar({ value: percent });

    if (percent >= 100) {
        window.location = "newLocation.html";
    } else {
        setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
    }                 
}
2
Dean

これは私がこのことをする私の音楽プロジェクトのために作ったタイマーです。すべてのデバイスで正確なタイマー。

var Timer = function(){
  var framebuffer = 0,
  var msSinceInitialized = 0,
  var timer = this;

  var timeAtLastInterval = new Date().getTime();

  setInterval(function(){
    var frametime = new Date().getTime();
    var timeElapsed = frametime - timeAtLastInterval;
    msSinceInitialized += timeElapsed;
    timeAtLastInterval = frametime;
  },1);

  this.setInterval = function(callback,timeout,arguments) {
    var timeStarted = msSinceInitialized;
    var interval = setInterval(function(){
      var totaltimepassed = msSinceInitialized - timeStarted;
      if (totaltimepassed >= timeout) {
        callback(arguments);
        timeStarted = msSinceInitialized;
      }
    },1);

    return interval;
  }
}

var timer = new Timer();
timer.setInterval(function(){console.log("This timer will not drift."),1000}
1
Code Whisperer

JavaScriptタイムアウトには、事実上10〜15ミリ秒の制限があります(185ミリ秒の実際のjs実行を行っていない限り、200ミリ秒を得るために何をしているのかわかりません)。これは、ウィンドウの標準タイマー解像度が15ミリ秒であるためです。これを改善する唯一の方法は、システム全体の設定であるWindowsの高解像度タイマーを使用することです。この問題に関するIntelのバグ)。

10-15msのデファクトスタンダードは、ウェブサイトで0msタイムアウトを使用している人々が10-15msタイムアウトを想定している方法でコーディングしているためです(たとえば、60fpsを想定し、デルタロジックなしで0ms/frameを要求するjsゲームゲーム/サイト/アニメーションは、意図したよりも数桁速くなります)。そのため、Windowsのタイマーの問題がないプラットフォームでも、ブラウザーはタイマーの解像度を10ミリ秒に制限しています。

0
olliej

ここに私が使用するものがあります。 JavaScriptなので、Frontendとnode.jsの両方のソリューションを投稿します。

両方の場合、同じ小数丸め関数を使用します。理由は次のとおりです。

_const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)
_

places-丸める小数点以下の桁数。これは安全であり、フロートの問題を回避する必要があります(1.0000000000005〜のような一部の数値では問題が発生する可能性があります)。私は、ミリ秒に変換された高解像度タイマーによって提供される小数を丸める最良の方法を研究するために時間を費やしました。

_that + symbol_-オペランドを数値に変換する単項演算子であり、Number()とほぼ同じ

ブラウザ

_const start = performance.now()

// I wonder how long this comment takes to parse

const end = performance.now()

const result = (end - start) + ' ms'

const adjusted = round(2, result) // see above rounding function
_

node.js

_// Start timer
const startTimer = () => process.hrtime()

// End timer
const endTimer = (time) => {
    const diff = process.hrtime(time)
    const NS_PER_SEC = 1e9
    const result = (diff[0] * NS_PER_SEC + diff[1])
    const elapsed = Math.round((result * 0.0000010))
    return elapsed
}

// This end timer converts the number from nanoseconds into milliseconds;
// you can find the nanosecond version if you need some seriously high-resolution timers.

const start = startTimer()

// I wonder how long this comment takes to parse

const end = endTimer(start)

console.log(end + ' ms')
_
0
agm1984

嫌いですが、これを軽減する方法はないと思います。ただし、クライアントシステムに依存すると思いますので、JavaScriptエンジンまたはマシンを高速にすると、精度が向上する可能性がありますわずかに

0
Eric Wendelin

私の経験では、jsが動作することをこれまでに認識した最小の妥当な時間は約32〜33ミリ秒であるにもかかわらず、労力が失われています。 ...

0
roenving

ここには間違いなく制限があります。少し見通しを与えるために、GoogleがリリースしたばかりのChromeブラウザは15-20ミリ秒でsetTimeout(function(){}、0)を実行できるほど速いのに対し、古いJavascriptエンジンは数百setTimeoutはミリ秒を使用しますが、現時点ではjavascript仮想マシンはその精度でコードを実行できません。

0
Bialecki

ダン、私の経験(JavaScriptでのSMIL2.1言語の実装を含む、時間管理が主題です)から、実際にはsetTimeoutまたはsetIntervalの高精度は必要ないことを保証できます。

ただし、重要なのは、キューに入れられたときにsetTimeout/setIntervalが実行される順序です。これは常に完全に機能します。

0
Sergey Ilinsky