趣味は車輪の再発明♪

JavaScriptで連続したイベント処理の体感速度を上げる方法



javascriptで、画面サイズの変更や、スクロール、マウスの移動のような、短い間に大量に発生するイベントで、画面を更新するような、少し時間のかかる処理を行うと、全体の動きがぎこちなくなって、パフォーマンスが著しく低下する場合があります。

非常に使いにくい。

これを回避するには、たとえばマウスが停止したときに処理すればよいのですが、そんなイベントはありません。


ではいつやるか?



イマデショ щ(゚д゚щ)



はい、すみません、違います(笑)。イマじゃなくってアトでやります。どうやるかというと、

「イベント発生で、タイマー起動。タイムアウトで処理をするのだけど、その前に同じイベントが発生したら、このタイマーを再起動」

と、これだけなんですが、単純にやっちゃうと、結構大変&邪魔くさい。いろんなところに似たようなことを書くことになりますから。保守性も悪いし。

ということで、

(*゚▽゚)ノけっこう便利なスクリプトをご紹介('-'*)

doLaterって関数です。ホント、自分で言うのもなんですが、便利なので、このサイトで多用しています。

/**
 * 後でやる。一回だけやる。
 * やる前に何回言われても一回だけ。
 *
 * @param job 後で実行する処理(関数)
 * @param tmo 実行待ち時間
 * @return なし
 * @author K.Takami
 * @version 2012-01-24
 */
function doLater(job, tmo) {
  //タイムアウト登録されているならキャンセル
  if(job in doLater.TID) {
    window.clearTimeout(doLater.TID[job]);
  }
  //タイムアウト登録する
  doLater.TID[job] = window.setTimeout(
      function() {
        //実行前にタイマーIDをクリア
        delete doLater.TID[job];
        //登録処理を実行
        try {
          job.call();
        } catch(e) {
          alert("EXCEPTION CAUGHT : " + job);
        }
    }, tmo);
}
//処理からタイマーIDへのハッシュ
doLater.TID = {};

処理自体(関数オブジェクト)をハッシュのキーにしてタイマーIDを覚えておくところがミソですね。 処理が違っていれば別タイマーで別管理となるわけで。

使い方はこんな感じ。

function onmouseover(mouseevent) {
	//100msマウスが停止したら処理実行
	doLater( function() {
		/*
		 * イベント処理(mouseeventが使用できますね)
		 */
	}, 100);
}

スクロールやドラッグ&ドロップ、画面サイズ変更時の、画面更新処理なんかに有用です。

インクリメンタルサーチにも必須の処理かと。

以下の適用例も参考にしてみてください。

適用例

マウス位置に関連する情報をサーバーから取得する(Google標高カラーマップより抜粋)

以下のフォームには、地図(Google Map)が表示されていて、マウスポインタを地図上の任意の位置に置くと、緯度・経度・標高データが表示されます。

緯度と経度はGoogleMapのイベントで得られ、標高データは Google Map のサーバーへのAjaxリクエストによって取得されます。

「遅延実行」チェックボックスにチェックが付いている状態では、上記doLaterを使用して、マウスポインタが停止してから50[ms]後に、標高を取得するようになっています。

チェックをはずしていると、イベントのたびにサーバーへリクエストが行われ、マウスを停止したあとも、しばらくの間、標高情報が更新され続けるような現象が見られます。

遅延実行
緯度:-
経度:-
標高:-

サンプルコード
$(function() {
    var map = new google.maps.Map(
      document.getElementById("map_canvas"),
      {
            zoom: 8,
            center: new google.maps.LatLng(35, 135),
            mapTypeId: google.maps.MapTypeId.ROADMAP
        });
  google.maps.event.addListener(map, "mousemove", onMouseMove);
});
function onMouseMove(mouseEvent) {
  var latLng = mouseEvent.latLng;
  if($("#chkDelay").attr('checked')) {
    // 遅延させる
    doLater(function() {updateElevation(latLng);}, 50);
  } else {
    //直接実行
    updateElevation(latLng);
  }
}
function updateElevation(latLng) {
  var elevationLoader = new GmapElevationLoader();
  elevationLoader.get([latLng],
      function(elevationResultArray, elevationStatus){
        $.each(elevationResultArray,
          function(i, elevationResult) {
            $('#mouseLat').text(
                Math.round(latLng.lat() * 10000) / 10000);
            $('#mouseLng').text(
                Math.round(latLng.lng() * 10000) / 10000);
            $('#mouseElv').text(
                Math.round(elevationResult.elevation * 100) / 100);
        });
      });
}

※ 何の説明もなく上で使っているGmapElevationLoaderクラスは、Google標高カラーマップで、大量の標高データをAPI制限に引っかかって謝りながらも得るために作った、google.maps.ElevationServiceのラッパークラスです。 これもなかなかインテリジェントなのですが、それなりに長いのでまた別の機会に。



RSS Feed Widget
JavaScriptで連続したイベント処理の体感速度を上げる方法
(http://bicycle.life.coocan.jp/takamints/index.php/techtips/delayedExecOnce)
by Koji Takami ( email: vzg03566@gmail.com, GitHub: takamin )