2007/09/30

JavaScriptで重い処理をするとアニメーションGIFが止まる件について

当分の間JavaScriptオンリーです。今日はですます調です。

Ajaxのローディングとかでクルクル回るアニメーションGIFてありますよね?あのローディングのアニメーションGIFをJavaScriptで重い処理をするときに使おうと思っていてその画像を探していました。そして、先日調べていたら、こんなサイトを発見しました。激しくいいですね。
Ajaxload - Ajax loading gif generator
実は、結構有名なサイトなんですね。知らなかったです。でも、先日知って、早速作ってみました。
ローディング画像

そして、早速使おうと思ったのですが、重い処理をしている間にアニメーションGIFを表示させていてもアニメーションがされないのです。。。つまり、アニメーションではない状態のGIF画像が表示されるだけなのです。その原因はJavaScriptが処理されている間だからです。また、重いJavaScriptが処理中の際にはその間はブラウザが固まります。

その解決方法を探していたのですが、その際にようやく私もsetTimeoutの使いかたがわかりました。そして、ページ描画のタイミングを制御できるようになりました。そうです。ポイントはsetTimeoutです。重い処理を一つの関数に入れちゃダメなんですね。つまり、これはダメなのです。

  1. Event.observe(window, 'load', function() {
  2.         var loadingImage = Builder.node('img', {src: 'loading.gif'});
  3.         var sync = $('sync');
  4.         var async = $('async');
  5.         var loading = $('loading');
  6.         var working = $('working');
  7.         var loop = $('loop');
  8.         Event.observe(sync, 'click', function() {
  9.           var breakNumber = 500;
  10.           var callback = function() {
  11.             // ここが重いということにする。例えば、以下はまぁまぁ重い。
  12.             while (breakNumber--) {
  13.               working.appendChild(Builder.node('div', {className: 'test'}));
  14.               document.getElementsByClassName('test');
  15.             }
  16.             loading.removeChild(loading.firstChild);
  17.             loop.innerHTML = 501 - breakNumber;
  18.             sync.disabled = false;
  19.           }
  20.           loading.appendChild(Builder.node('div', [loadingImage, "Loading..."]));
  21.           sync.disabled = true;
  22.           setTimeout(callback, 0);
  23.         });
  24.      });

あ。ちなみにprototype.jsとscript.aculo.usがあることを前提に書いているので、その辺は適当にほげほげしてください。まぁ、ここではsetTimeoutを使用して、その中で重い処理を実行していますね。処理を始める前にアニメーション画像を出して、処理が終わる際にローディングの画像を消す処理をしています。そして、while文の中を勝手に重い処理としてここでは使用しています。しかし、このwhile文が流れている間、いや正確にはcallback関数が実行されている間はアニメーションGIFが止まります。

そこで、アニメーションGIFを止めないような方法を考えました。その根本にある考えは。。。重い処理は一つの関数に入れないということです。つまり、一つ関数の中にループで重い処理を実行させるのではなく、再帰を使用して何個も関数を呼び出す必要があります。もちろんそれが再帰処理ができないような重い処理であればこの方法は無理ですが、往々にして重い処理というのはDOMを使ってイテレーションを使用するところになる可能性が高いと思いますので、それを再帰処理に置き換えましょう。そして、修正したコードは以下の通りです。

  1. Event.observe(window, 'load', function() {
  2.         var loadingImage = Builder.node('img', {src: 'loading.gif'});
  3.         var sync = $('sync');
  4.         var async = $('async');
  5.         var loading = $('loading');
  6.         var working = $('working');
  7.         var loop = $('loop');
  8.         Event.observe(async, 'click', function() {
  9.           var breakNumber = 500;
  10.           var callback = function() {
  11.             if (breakNumber--) {
  12.               working.appendChild(Builder.node('div', {className: 'test'}));
  13.               document.getElementsByClassName('test');
  14.               setTimeout(callback, 0);
  15.             } else {
  16.               loading.removeChild(loading.firstChild);
  17.               async.disabled = false;
  18.             }
  19.             loop.innerHTML = 501 - breakNumber;
  20.           };
  21.           loading.appendChild(Builder.node('div', [loadingImage,"Loading..." ]));
  22.           async.disabled = true;
  23.           setTimeout(callback,0);
  24.        });
  25.       });

これで、重いループををしていても、だいたいの場合はアニメーションGIFが止まらずに済みます。「だいたいの場合」と書いたのは理由があります。実は、私の実環境では、もっと重い処理をしなければいけない用件がありましたので、修正版でもアニメーションGIFが動きませんでした。正確にはfirefoxでは大丈夫でした(たぶんそれほど重くなかったのが原因かもしれません。)が、IE6ではアニメーションGIFが止まりました。原因は、再帰の中で呼び出しているsetTimeoutのmillisecondの値でした。私はどうせ次の処理を実行してもらうのだから0でいいのではないか、と考えていたのですが、0だとすぐ関数をエンキューするのはいいのですが、現在走っている処理が終わると、すぐキューにたまった関数をデキューして実行するので、JavaScriptの処理が常に動いていることになったようです。あくまで想像ですが。なので、ここにラグを与える必要があったのです。そこで、setTimeoutに渡すmillisecondの値を適当な数値に変更すると、カクカクするけど、アニメーションGIFが動いてくれました。
動くサンプルは以下に置いておきます。ちなみにこのサンプルはカクカクしません。
Animation GIF and JavaScript setTimeout

404 Blog Not Found:javascript - ページはいつ再描画されるかが激しく参考になりました。

先日、jQuery開発者向けメモを見て、一通り試してみましたがなかなか直感的に書けそうなライブラリなので今後使用することを検討してみます。今、開発の勢いが一番あるライブラリなので追いかけたりするのが楽しそうですね。prototype.jsとscript.aculo.usは、なかなか新たなリリースが無く、ちょっとさみしいですが、これはこれでライブラリとして成熟段階に入ったといことでアリなのでしょうね。

さて、本日はTOEICを受けてきました。過去に2回ほど受けたのですが、相変わらず時間が足らなかったです。問題が中途半端に全問解けそうなので、ついついちゃんと読んでやってしまうのが原因です。そのため最後に15問ほど残ってしまいます。スピード勝負はつらいですね。でもまぁ、おそらくそれなりのスコアは出たでしょう。試験地の愛知大学が思ったよりもとてもキレイでした。

先日激しくヘコむことがありましたが、ようやくすっきりしました。今日から強く生きていこうと思います。

おぉ、ちょうどIE6でアニメーションGIFが止まってしまう現象にあって右往左往していたので助かりました。

コメント by ZARU — 2007/10/02

ZARUさん

コメントありがとうございます。助けになって何よりです。

コメント by shin — 2007/10/02

Leave a comment

Bloglines feedburner