bugfix> javascript > 投稿

この問題に遭遇したとき、私はウェブプログラミングを練習していました。 問題は、forループで円の半径を繰り返し増分しようとすると、各反復後の変更の結果が表示されないことです。ただし、最後の反復で結果が表示されます。半径40が表示されます。ここで何が問題なのでしょうか?

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function strokeincr(){
var circl = document.getElementById("circ");
var i;
for(i=10;i<=40;i++)
{
alert("for i="+i);//this was just to see the value of i changing.
            circl.setAttribute("r",i);
       }
    }
</script>
<style>
    #div1 svg{
        width:200px;
        height: 200px;
        background-color:pink;
    }
</style>
</head>
<body>
<div id="div1">
<svg>
  <circle id="circ" cx="100" cy="100" r="10" fill="green"></circle>
</svg>
</div> 
<button onclick="strokeincr()"> click me</button>
</body>
</html>

回答 4 件
  • さて、最初にアラートはプロセスを停止しますが、より重要なのは for loop  中括弧の間でコードを実行するようプログラムに指示します。可能な限り迅速かつ効率的に実行します。 @YonaAppletreeが指摘したように、公式の仕様には書かれていませんが、ほとんどのブラウザー(すべてではないにしても)は同じまたは些細な処理ルールを実装し、forループ中にビューの再描画を許可しません。これはリソースを節約するための最適化であり、実際にはエンドユーザーの観点から大きな違いはありません。これを行わなくても、コードはほとんどの場合、人間の目が認識できるよりも速く処理されるからです。これは、最適な最適化であることを意味しますが、現在のコードが期待どおりに機能しないことも意味します。

    私たちにできることは、 i の後に自動的にクリアするインターバルタイマーを設定することです  40以上になります。間隔は2つのパラメーターを取ります

    setInterval( function , time in ms )

    ms  ミリ秒です。 1000ミリ秒は1秒に相当します。指定された機能は、間隔がクリアされるまで、指定された間隔で何度も繰り返されます。例えば:

    setInterval( function() { console.log("hi!") }, 1000)

    「hi!」を印刷します1000ミリ秒ごとにコンソールに送信されます。これは1秒に相当します。クリアされないため、これは停止しません。

    この関数を変数に割り当てることにより、一度 i をクリア(関数を停止)できることを確認します。  40以上に達する。

    これは、変数に間隔を割り当てる方法です。

    var myIntervalTimer = setInterval(function, time in ms)

    その間隔をクリアするには、 clearInterval( name of interval ) を呼び出します 。例えば:

    clearInterval(myIntervalTimer)

    それから、すでに確立されているコードにプラグインするだけで、できあがりです!

    var i = 10;
    var myIntervalTimer = setInterval(function() {
                 console.log("i is" + i);
                circl.setAttribute("r",i);
                if(i >= 40) clearInterval(myIntervalTimer);
                i++;
    }, 250);
    
    

    円の半径を1/4秒(250ms)ごとに増やし、 i の場合に間隔タイマーをクリアするタイマーがあります。  40以上になります。

    <!DOCTYPE html>
    <html>
    <head>
    <script type="text/javascript">
    function strokeincr(){
    var circl = document.getElementById("circ");
    var i = 10;
    var myIntervalTimer = setInterval(function() {
                circl.setAttribute("r",i);
                if(i >= 40) clearInterval(myIntervalTimer);
                i++;
    }, 250);
    }
    </script>
    <style>
        #div1 svg{
            width:200px;
            height: 200px;
            background-color:pink;
        }
    </style>
    </head>
    <body>
    <div id="div1">
    <svg>
      <circle id="circ" cx="100" cy="100" r="10" fill="green"></circle>
    </svg>
    </div> 
    <button onclick="strokeincr()"> click me</button>
    </body>
    </html>
    
    

  • ここでの問題は、ブラウザが画面を更新する方法とイベントがどのように機能するかに関係しています。ブラウザには、「イベントループ」と呼ばれるものがあります。ブラウザは、ユーザーがボタンをクリックするなど、何らかのイベントが発生するのを待ってから、関連するコードを実行します。 setAttribute呼び出しのようなドキュメントを変更する呼び出しは、画面を直接更新しません。むしろ、ブラウザが画面に表示する内容を定義するDOM(ドキュメントオブジェクトモデル)を更新しています。

    すべてのイベント処理コードが終了した後にのみ、画面が更新されます。したがって、10から40までループし、円の半径を更新します。ただし、ブラウザーは、ループの終了後に値の確認のみを行います。したがって、最終的な値のみが表示されます。

    @zfrischの答えで述べたように、これを解決する1つの方法は、ブラウザーのイベントスケジューリング機能を使用することです。これらの機能を使用すると、後でイベントを発生させる(および選択したコードで処理する)ようにブラウザに要求できます。

    3つの主なスケジューリング機能を使用できます: setTimeout(callback, delayInMs)setInterval(callback, intervalInMs) 、および requestAnimationFrame(callback) 。詳細についてはそれらのドキュメントを参照できますが、一般的にはアニメーションには requestAnimationFrame を使用する必要があります。

    requestAnimationFrame  基本的には、すべてをレンダリングし、何か新しいものをレンダリングする準備ができたら、後でいくつかのコードを実行するようブラウザに要求します。

    requestAnimationFrame を使用する 、例は次のようになります。

    <!DOCTYPE html>
    <html>
    <head>
      <script type="text/javascript">
        function strokeincr() {
          // Note the use of const instead of var. It indicates a variable that we won't change, and ensures that we don't change it.
        
          const circl = document.getElementById("circ");
          
          // We need to know how long our animation should last, so we can compute where we are.
          // Note that since the varaible contains milliseconds, we label it as such for clarity.
          const animationDurationMs = 1000;
          
          // The starting time of the animation (in milliseconds since the epoch), so we can compute where we are in the animation later.
          const startTimeMs = Date.now();
          
          // Define the starting and ending values for the animation for later computation
          const startRadius = 10;
          const endRadius = 40;
          
          function incrementAnimation() {
            // Get the now-current time so we can compute the animation progress
            const currentTimeMs = Date.now();
            
            // Compute where we are in the animation as a fraction, ensuring we don't go over 1, using Math.min
            const animationFraction = Math.min(
                1,
                (currentTimeMs - startTimeMs) / animationDurationMs
              );
            
            // Compute what the current radius should be, given where we are in the animation, using liner interpolation
            const currentRadius = startRadius + (endRadius - startRadius) * animationFraction;
            
            // Update the circle with the new radius
            circl.setAttribute("r", currentRadius);
            
            // Check if there is more animating to do, and if so, request that this function is run again after the screen is updated.
            if (animationFraction < 1)
              requestAnimationFrame(incrementAnimation);
          }
          
          // Start the animation by calling the animation function directly.
          incrementAnimation();
        }
      </script>
      <style>
        #div1 svg {
          width: 200px;
          height: 200px;
          background-color: pink;
        }
      </style>
    </head>
    <body>
      <div id="div1">
        <svg>
          <circle id="circ" cx="100" cy="100" r="10" fill="green"></circle>
        </svg>
      </div>
      <button onclick="strokeincr()">click me</button>
    </body>
    </html>
    
    

  • ボタンをクリックすると、ループ全体が実行されます。ボタンをクリックするたびに円の半径を増やしたい場合は、別のアプローチをとる必要があります。

    function strokeincr() {
        var circl = document.getElementById("circ");
        var r = parseInt(circl.getAttribute("r"));
        circl.setAttribute("r", r + 10);
    }
    
    

  • これは非常に興味深い質問です。できる限り答えようとします。

    ブラウザの最適化により、予期しない動作が発生しました。再描画に関係しています。ループのステップごとに円の半径を変更しています。ブラウザが繰り返しごとに画面を再描画する必要がある場合、それは非常に要求の厳しい操作になります。

    では、ブラウザベンダーは何をしましたか?ループが終了するまで、(明らかに)再ペイントを遅らせました。理にかなっていますか?

    これは、アラートを使用してループを一時停止し始めたときに突然表示されました(繰り返しごとにDOMの値が変化しますが、画面は再描画されません)。

    この変更をアニメーション化する場合は、いくつかのアプローチがあります。 CSSトランジションを使用するか、JavaScript window.requestAnimationFrame() を使用できます 。しかし、それはあなたの質問の範囲を超えています;)。

    PS:この答えは、私自身の観察と私が少し前に読んだ記事に基づいているので、この再描画の動作に関する公式文書を知っているならコメントしてください(私自身もちょっと興味があります)。

あなたの答え