このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

JavaScriptでシーケンシャルな作業をゆっくりと行う方法

次の画面ショットは、Catyに描かせた「ログインモジュールを可視化した図」を、ブラウザで表示させたものです*1

実際のWebページには、次のURLでアクセスできます。

このWebページに含まれる図はラスター画像ではなくて、SVGHTML5仕様に従い直接埋め込んでいます(インラインSVG、ソースを見ると確認できます)。SVGの部分も文書のDOMツリーのなかに現れるので、JavaScriptで操作することができます。例えば、ボタンを押すことでモジュールの図(ハイパーリンク・グラフ)の一部を赤く塗ることができます。

しかし、赤く塗る作業が一瞬で終わってしまうので面白くない、とKuwataさんに言われました。jsAnim(http://jsanim.com/)とJSDeferredhttp://cho45.stfuawsc.com/jsdeferred/doc/intro.html)あたりを組み合わせてなんとかしようか、とも思ったのですが、どうせ使い捨てで大したことはしないので、ツギハギして小さなライブラリを作ったほうが手っ取り早いし、それで十分だろうと判断しました。

「タスク」と呼ぶことにした“作業を行うオブジェクト”達 task1, task2, ... を配列にしたものを、先頭から順にゆっくりと実行するためのライブラリ taskman.js を作りました。70行(実質は40行程度)しかないソースなので、このエントリーの最後に貼り付けます。http://www.chimaira.org/misc/fire2.html の [fire] というボタンがデモになっています。乱数を使っているので、赤く塗られる経路は実行ごとに変化します*2

ここでのタスクとは、init() と step() というメソッドを備えたオブジェクトです。init()でまず初期化してから、step()が一定の間隔で呼び出されます。step() がnullを返したら作業終了というお約束です。作業が継続するときのstep()の値は、null以外なら何でもいいのですが、そのオブジェクト自身を戻すようにすると、task.init().step().step() のようなメソッドチェーンが書けてちょっと便利です。

taskman.jsに含まれるtaskman.Nopとtaskman.Idleがタスクの(つまらない)例です。http://www.chimaira.org/misc/fire2.html では、fire2.js内のfire.Burnが赤く色を塗るタスクになっています。このサンプルを見れば、使い方は分かると思います。

// -*- coding: utf-8 -*-
// taskman.js

taskman = {};
taskman.running = false;

taskman.start = function(tasks, timeSlice_) {
    if (taskman.running) {
	return;
    }
    taskman.tasks = tasks;
    if (!(taskman.tasks instanceof Array) || taskman.tasks.length === 0) {
	return; // nothing to do
    }
    if (typeof timeSlice_ === 'undefined') {
	taskman.timeSlice = 40;
    } else {
	taskman.timeSlice = timeSlice_;
    }
    taskman.running = true;
    taskman.current = 0;
    taskman.task = taskman.tasks[taskman.current];
    taskman.task.init();
    taskman.step();
};

taskman.step = function() {
    if (!taskman.running || !taskman.task) return;
    var result = taskman.task.step();
    if (result === null) {
	taskman.current++;
	if (taskman.tasks.length <= taskman.current ) {
	    taskman.running = false;
	    taskman.current = -1;
	    taskman.task = null;
	    return;
	}
	taskman.task = taskman.tasks[taskman.current];
	taskman.task.init();
    }
    setTimeout("taskman.step()", taskman.timeSlice);
};

taskman.Nop = function () {
    if (!(this instanceof taskman.Nop)) return new taskman.Nop();
    return true;
};
taskman.Nop.prototype.init = function () {
    return this;
};
taskman.Nop.prototype.step = function () {
    return null;
};

taskman.Idle = function (count) {
    if (!(this instanceof taskman.Idle)) return new taskman.Idle(count);
    this.count = count;
    return true;
};
taskman.Idle.prototype.init = function () {
    return this;
};
taskman.Idle.prototype.step = function () {
    if (this.count <= 0) return null;
    this.count--;
    return this;
};

// end

*1:この図はたまたま日本語を使ってませんが、日本語も使えますよ。http://d.hatena.ne.jp/m-hiyama/20111111/1320987809 を参照。

*2:もちろん、同じ経路が何度も続くこともあります。