jQueryで日本語テキストをシャッフルさせる

jquery.text-effect(grab bag)のscrambledWriterを日本語が使えるように書きかえた。
JSFiddleでのDemoをここに。あとjsdo.itにも投げてみた
String.prototype.shuffleの処理はここから拝借した。文字列のprototypeを拡張しているのに何も考えずそのままコピペしているところがアレですね。コピペコーディングおいしいです。



スクリーンショット

作業記録

1.探す

  1. なんかテキストエフェクト系でカッコいいjQueryプラグインないかなー、とか思い立つ。
  2. Googlejquery plugin text effectとか聞いてみる。
  3. 色々リンクを踏んで上記のサイト(grab bag)を見つける。
  4. Demoを見てかっこいい(ボソッ と言う。
  5. ダウンロードしローカル環境で動かしてみてなぜか悦に浸る。
  6. でもscrambleWriterの日本語の表示がいけてないことに気づく。アルファベットと数字ばかり…。

2.読む

JavaScriptjQueryもちょっとだけ書いたことがあるだけでよく知らないけど、いい機会だしソースコードを読んでみる。scrambledWriterを実装している箇所は以下。

    $.fn.scrambledWriter = function() {
        this.each(function() {
            var $ele = $(this), str = $ele.text(), progress = 0, replace = /[^\s]/g,
                random = randomAlphaNum, inc = 3;
            $ele.text('');
            var timer = setInterval(function() {
                $ele.text(str.substring(0, progress) + str.substring(progress, str.length).replace(replace, random));
                progress += inc
                if (progress >= str.length + inc) clearInterval(timer);
            }, 100);
        });
        return this;
    };

jQueryプラグインを作る際は$.fn.(使いたい名前)に関数を入れるらしい。
短いコードなのでこれを頭から順ぐりに読む。わからない記述は調べつつ。

this.each(...)

これは選択されたエレメントに対して(...)内の関数を実行する。
それにしてもthisとはなんなのか。
次の要素を考えて、

<div class="bocchan">親譲りの無鉄砲で小供の時から損ばかりしている。</div>
<div class="bocchan">だから清の墓は小日向の養源寺にある。<div>

これにたいしてconsole.logを打ってみる。

$.fn.test = function(){
  console.log(this.html());
}
$(".bocchan").test();
//=>[div.bocchan, div.bocchan]

なるほど、thisには$(".bocchan")によってセレクトされた要素の配列が入っているのか。
つまりthisは$(".bocchan")の結果に関連付けられているらしい。ではeachの中身ではthisはどうなっているのか。

$.fn.test = function(){
    $(this).each(function(){
        console.log(this);
        console.log($(this).html());
    }
    );
}
$(".bocchan").test();

結果

<div class="bocchan">
親譲りの無鉄砲で小供の時から損ばかりしている。
<div class="bocchan">
だから清の墓は小日向の養源寺にある。

それぞれのDOM要素を保持しているようだ。
一般的に、thisは実行中の関数のオブジェクト自身を指し示すものであるらしい。
関数が実行される際には、実行コンテキストというものが生成される。それがオブジェクトと関連付けられるとのこと。


参考:
JavaScript の this について -IT戦記
変数のスコープについて


続いてeachで実行する関数の中身を見ていく。まず変数の初期化。

var $ele = $(this), str = $ele.text(), progress = 0, replace = /[^\s]/g,
    random = randomAlphaNum, inc = 3;

$eleの頭についている$はなんなのかよく分からない。jQueryオブジェクトであることを示しているのだろうか。
randomAlphaNumは以下のような関数である。

function randomAlphaNum() {
    var rnd = Math.floor(Math.random() * 62);
    if (rnd >= 52) return String.fromCharCode(rnd - 4);
    else if (rnd >= 26) return String.fromCharCode(rnd + 71);
    else return String.fromCharCode(rnd + 65);
}

名前から想像はつくけど、Math.random()とかfromCharCodeとか使ってるからランダムなアスキー文字を返す関数だろう。中身はちゃんと読んでないがそうに違いない。
よってrandomはscrambleしている部分に表示する文字だと予想できる。
アスキー文字しか生成してないから日本語が表示されていなかったのだ。次。

$ele.text("");
var timer = setInterval(function() {
    $ele.text(str.substring(0, progress) + str.substring(progress, str.length).replace(replace, random));
    progress += inc
    if (progress >= str.length) clearInterval(timer);
}, 100);

setIntervalで100msごとに中身の関数を実行している。
setIntervalの返り値はタイマーIDで、clearIntervalで処理を停止するときに使う。これtimerの初期化タイミングどうなってるんだろ…
さて、

$ele.text(str.substring(0, progress) + str.substring(progress, str.length).replace(replace, random))

は選択したタグ内のテキストstrを頭からprogressまではそのまま、progressから最後までをランダムな文字列にしている。
scrambleWriterの主要な処理と言える。

書きかえる

よって日本語を表示するには、

$ele.text(str.substring(0, progress) + str.substring(progress, str.length).replace(replace, random))

の後半行を適当なランダム文字列を作る処理に作り替えてあげれば良いようだ。
今回はString.prototype.shuffleを定義することによりこれを実現した。
ただこの方法には欠陥があって、シャッフルする文字列が同じ文字ばかりだと全然scrambleな表現にならない。

(function($) {
    String.prototype.shuffle = function () {
        var a = this.split(""),
            n = a.length;
  
        for(var i = n - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
        }
        return a.join("");
    }
    $.fn.scrambledWriter = function() {
        this.each(function() {
            var $ele = $(this), str = $ele.text(), progress = 0,replace = /[^ -~。-゚]/g , inc = 3;
            $ele.text('');
            var timer = setInterval(function() {
                $ele.text(str.substring(0, progress) + str.substring(progress, str.length).shuffle());
                progress += inc
                if (progress >= str.length + inc) clearInterval(timer);
            }, 100);
        });
        return this;
    };
})(jQuery);

どこにつまづいたか

randomAlphaNumを見てこれをランダムな日本語文字を返す関数にすればいいんじゃね? と考え日本語の文字コードを調べはじめてUTF-8のコード表とか眺めてよくわからないドツボに勝手にはまった。

次になにをするか

オプションでエフェクトの速度が決められないのはおかしい。inc=3ってなによ。
デフォルト引数を与える方法を調べる。

$.fn.scrambledWriter = function(inc){
   if(inc == void 0) inc = 3;
   //  inc = inc || 3;
}

こんな感じでいいのだろうか。