web-dev-qa-db-ja.com

forループ内のsetTimeout

次のコードを使用して、文字列を文字ごとに表示したいと思います。

function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

    for(c = 0; c < text.length; c++)
    {
        setTimeout('textScroller.innerHTML += text[c]', 1000);
    }
}

window.onload = initText;

それは機能していません..私は何が間違っているのですか?

16
richard

次のようなものを試してください。

function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

    var c = 0;
    var interval = setInterval(function() { 
                          textScroller.innerHTML += text[c]; 
                          c++; 
                          if(c >= text.length) clearInterval(interval);
                   }, 1000);

}

必要なときに停止するためにclearIntervalを追加したことに注意してください。

35
Soufiane Hassou

現在、18のタイムアウトを定義しており、すべてが一度に実行されます。 2番目の問題は、文字列として実行する命令を渡すことです。その場合、評価されたコードはグローバルスコープで実行されるため、コードはinitTextで定義されたすべての変数にアクセスできません。

IMO、これでうまくいくはずです

function initText(){
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

    var c = 0;

    (function(){
        textScroller.innerHTML += text.charAt(c++);
        if(text.length > c){
            setTimeout(arguments.callee, 1000);
        }
    })();
}
4
Rafael

answer by @ yauhen-yakimovichよりもさらに一般的です:

Timeoutの使用:

_var repeat = (function () {
    return function repeat(cbWhileNotTrue, period) {
        /// <summary>Continuously repeats callback after a period has passed, until the callback triggers a stop by returning true.  Note each repetition only fires after the callback has completed.  Identifier returned is an object, prematurely stop like `timer = repeat(...); clearTimeout(timer.t);`</summary>

        var timer = {}, fn = function () {
            if (true === cbWhileNotTrue()) {
                return clearTimeout(timer.t); // no more repeat
            }
            timer.t = setTimeout(fn, period || 1000);
        };
        fn(); // engage
        return timer; // and expose stopper object
    };
})();
_

Intervalの使用:

_var loop = (function () {
    return function loop(cbWhileNotTrue, period) {
        /// <summary>Continuously performs a callback once every period, until the callback triggers a stop by returning true.  Note that regardless of how long the callback takes, it will be triggered once per period.</summary>

        var timer = setInterval(function () {
            if (true === cbWhileNotTrue()) clearInterval(timer);
        }, period || 1000);
        return timer; // expose stopper
    };
})();
_

コメントに示されている2つのわずかな違い-repeatメソッドは、コールバックの実行後にのみ繰り返されるため、「遅い」コールバックがある場合、delayミリ秒ごとに実行されるわけではありませんが、repeatsは、実行の間にdelayごとに実行されますが、loopメソッドはdelayミリ秒ごとにコールバックを起動します。途中で停止するには、repeatは返される識別子としてオブジェクトを使用するため、代わりにclearTimeout(timer.t)を使用します。

使用法:

ちょうど answer by @ soufiane-hassou:

_var textScroller = document.getElementById('textScroller');
var text = 'Hello how are you?';

var c = 0;
var interval = repeat/* or loop */(function() { 
                      textScroller.innerHTML += text[c]; 
                      c++; 
                      return (c >= text.length);
               }, 1000);
_

前述のように、早期停止は次のようになります。

_/* if repeat */ clearTimeout(interval.t);
/* if loop */   clearInterval(interval);
_
3
drzaus

これを試して:

function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';

for(c = 0; c < text.length; c++)
{
    setTimeout("textScroller.innerHTML += '" + text[c] + "'", 1000 + c*200);
}
}

window.onload = initText;
2
Josh Pearce

クロージャを使用してみてください:

function init() {
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';
    var c = 0;
    function run() {
        textScroller.innerHTML += text[c++];
        if (c<text.length)
            setTimeout(run, 1000);
    }
    setTimeout(run, 1000);
}
init()

コードの問題は、文字列に入力したコードが、textScrollerが定義されていない(関数内で定義されている)グローバルコンテキストで実行されることです。

1
idrosid

スニペットを共有したい(Soufiane Hassouの回答に基づく)。これは、文字通りforループ本体を置き換えて、一定の時間間隔で配列を反復処理する場合にまで及びます。基本的に同じ同期ループですが、「スリープ」一時停止があります(javascriptは同期プログラミング言語ではないため)。

function loop(arr, take, period) {
    period = period || 1000;
    var i = 0;
    var interval = setInterval(function() { 
        take(i, arr[i]);
        if (++i >= arr.length) { clearInterval(interval);}
    }, period);
}

使用例:

loop([1, 2, 3, 4], function(index, elem){
    console.log('arr[' + index + ']: ' + elem);
});

Node JSでテスト済み。誰かの助けになることを願っています。

編集>

次の更新により、コードは、重い「プロトタイピング」(jQueryやプロトタイプなど)を実行するライブラリと一緒に使用できるようになります。

function loop(arr, take, period) {
    period = period || 1000;
    var scope = {
        i: 0,
        arr: arr,
        take: take,
    };
    var iterate = (function iterate() {
        if (this.i >= this.arr.length) { clearInterval(this.interval); return}
        take(this.i, this.arr[this.i++]);
    }).bind(scope);
    scope.interval = setInterval(iterate, period);
}
1

(setIntervalの代わりに)setTimeOutを保持し、(setTimeOut呼び出しでコードブロックを評価する代わりに)名前付き関数を使用する場合、これは役立つ可能性があります。

_var b = {
  textScroller: document.getElementById('textScroller'),
  text: "Hello how are you?"
};


function initText() {
  for(c = 0; c < b.text.length; c++) {
    setTimeout("append("+c+")", 1000 + c*200);
  }
}

function append(c) {
  b.textScroller.innerHTML += b.text[c];
}

window.onload = initText;
_

上記を使用すると、関数を追加するためのパラメーターを渡すことができます。

いくつかのパラメーターを渡すために、次のコードがトリックを実行します。

_var glo = [];

function initText()
{
  var textScroller = document.getElementById('textScroller');
  var text = "Hello how are you?";
  var timeout_time;
  for(c = 0; c < text.length; c++) {
    glo[glo.length] = {text:text, c:c, textScroller:textScroller};
    timeout_time = 1000 + c * 200;
    setTimeout("append(" + (glo.length - 1) + ")", timeout_time);
  }
}

function append(i)
{
  var obj = glo[i];
  obj.textScroller.innerHTML += obj.text[obj.c];
  obj = null;
  glo[i] = null;
}

window.onload = initText;
_

上記では、グローバル配列gloは1つだけです。ループでは、gloに新しい配列メンバーを作成し、append()関数では、パラメーターとして渡されるインデックスを使用してこれらのメンバーを参照します。

注意:2番目のコードサンプルは、OP:sの問題に対する最善または最適な解決策として意図されたものではありませんが、他のsetTimeOut相対問題で役立つ可能性があります。誰かがプレゼンテーションやパフォーマンステストを行いたいときに、遅延後にいくつかの機能を呼び出す必要がある場合。このコードの利点は、forループ(多くのコーダーがforループを使用したい)を利用できることと、内部ループも使用できることと、ループ時間状態のローカル変数をtimeOut関数に「送信」する機能です。

0
Timo Kähkönen

Forループは、すべての文字に一度にタイムアウトを設定しているため、文字は順番に表示されませんが、一度に表示されます。 setTimeoutには、表示する次の文字を含む別のsetTimeoutへのコードを含める必要があります。

だからこのようなもの(これをテストしなかった)

function initText()
{
    var textScroller = document.getElementById('textScroller');
    var text = 'Hello how are you?';    
    setTimeout('nextChar(text)', 1000);
}
function nextChar(text){
    if(text.length > 0){
        textScroller.innerHTML += text[0]; 
        setTimeout('nextChar(text.substring(1))', 1000);
    }
}
0
Jarrett Widman

カスケードでループする方が良い場合があります。たとえば、divをフェードする場合:

div=document.createElement('div');
div.style.opacity=1;
setTimeout(function(){fade(1);},3000);
function fade(op){
    op-=.05;
    if(op>0) setTimeout(function(){div.style.opacity=op;fade(op);},30);
    else document.body.removeChild(div);
}
0
sojahu