スマホの方はこちらも御覧下さい→

困ったので

最近、PC版サイドバーに設置していた忍者ビジター(髭のおじさんver.のオンラインカウンター)が表示されなくなったので、忍者の管理画面で確認したところ、アニメーションカウンターがいずれも真っ白になっていて、原因もわかりませんでした。
たぶんフラッシュの不具合とかで時間を置けば直ると思いますが、この際フラッシュに頼らないカウンターを自分で作ってみることにしました。

と言ってもデータを書き込んだりするこのオンラインカウンターというのは、規約上ライブドアブログのサーバーを使えませんから、正攻法で作るなら自分でサーバーを用意しなければなりません。
でも、書き込むデータというのは閲覧者人数を表すわずか数ビットの数値で、そのためだけに月額千円とか払いたくないわけです。
そこで発想を転換し、サーバーそのものではなく、サーバーからブラウザに送られたデータを参照すれば JavaScript だけでオンラインカウンターが作れるかも作れるぞ作ってみよう!
ということになったわけです。
まあ正確にはカウンターではなくカウンターの表示スクリプトなんですが

忍者の数字を盗み見るゾ

テキストタイプの忍者カウンターのタグは、
<script type="text/javascript" src="http://(ユーザーID).vis1.shinobi.jp/js/" charset="utf-8"></script>
この一行だけ。
閲覧者様がこのページを読み込むとこのソースのスクリプトが実行され、忍者のサーバーに「閲覧者一名様追加!」という情報が伝えられます。
そして、その時に集計した最新の情報がブラウザに返されて、画面に人数が表示されます。
この「画面に表示された人数」を参照しようというわけです。

忍者のサービスの規約で、上記タグの改変などはもちろん御法度ですから、これには一切手をつけず、そのまま残します。

数字を参照するには、このスクリプトで生成された DOM要素にアクセスすればいいので、まず普通にブログ画面を開き、カウンターを右クリック。
メニューから「要素を調査」を選択すると、ブラウザの開発者ツールが開き、忍者カウンターのソースの最終形が表示されます。
ninja-counter-dom
ありがたいことに、数字を含む要素に ID がついています。
IDがないと、目的の要素の操作はいろいろ面倒なステップを踏まなきゃいけなくなるので、だいぶ助かりますね。

閲覧人数の取得

この「現在 12 人が閲覧しています」という文字列から数字だけを取り出すには
    var s = $('#viewninjamsg').html();
s = parseInt(s.replace(/[^0-9]/g,''));
としてやります。
文字列置換メソッドのreplace()の引数 /[^0-9]/g,'' は、0~9以外の文字をすべて空文字に、という意味の正規表現です。
これで変数s に「12」という文字列が代入されました。
文字列なので、parseInt()関数で数値に変換します。
以上の方法で、自前のサーバーを使わずに、ライブドアや忍者の規約にも反せずに、閲覧者人数を取得できました。やったね!

あとはこの数字を元に、オリジナルのカウンター画面を生成すればいいわけです。
本当は咲-Saki-キャラのアイコンなんかを使いたいんですが、まあそれはおいおい。
今回は手っ取り早く、すでにある麻雀牌のスプライト画像を使いました。

定数の定義

var XX = 180, YY = 90, xx = 20, yy = 29;
XX,YY は表示領域の、xx,yy は麻雀牌の、それぞれ幅と高さです。
要素のCSSを読み込むようにした方が拡張性があるので、最終的にはそのようにしたいと思います。
for(var i=0;i<s;i++){
    (アイテムの生成)
}
この for文の中でs個のアイテムすなわち

麻雀牌を生成します

  var cls = pai[Math.floor((Math.random())*pai.length)];
  var html = '<div><span class="ancoro tile_'+cls+'" title="咲さんかわいい!"></span></div>';
弊ブログの牌画表示に使っているスプライト画像のクラス名(配列にしてある)をランダムに選び、div要素の属性にします。
ついでにマウスオーバー時にポップアップする文章も入れてみました。
今回はすべて「咲さんかわいい!」のみ。
  var $div = $(html);
ここ、普通の JavaScript だと
  var $div = document.createElement('div');
  $div.innerHTML = html;
みたいに書きますが、jQuery だと一行で済むという。

生成した div要素にCSSを設定

  $($div).css(
    {
      top: Math.floor(Math.random()*YY-(yy/2)) + 'px',
      left: Math.floor(Math.random()*XX-(xx/2)) + 'px'
    }
  );
ここでは表示する座標をランダムに当てつつ、最大・最小の時に牌の半分ぐらいが隠れるように調整しています。
  $('#visitors').append($div);
div要素を親要素に紐つけます。
これを s回繰り返して、初期設定はおしまい。

アニメーションさせる

アニメをループさせるのに普通は再帰関数という手法を使うわけですが、どうも私はこれをよく理解できておらず、ちょっと変なコードになっていると思います。
function moving(obj){
  $(obj).animate(
    {
      top: Math.floor(Math.random()*YY-(yy/2)) + 'px',
      left: Math.floor(Math.random()*XX-(xx/2)) + 'px'
    },
    Math.floor(Math.random()*10000)
  );
}
移動先の座標とアニメの時間をランダムに指定しています。
この関数の最後に
  moving(obj);
と、自分自身を呼び出すようにしてやりたいんですけど、それだとどうしても「再帰が多すぎ!」というエラーが出て、うまくいきませんでした。
一度に多くの要素を動かしているせいなのか、試しに動かす牌を一つだけにしてみたら、ちゃんと動いたんですが…ヨクワカラン
すこやん誕生日の記事で Coco の背景に使ったアニメーションでも同じような問題が起きたのですが、結局今回もその時と同じゴマカシっぽい方法で妥協しました。
つまり

再帰ではなく、普通にループさせる

for(var j=0;j<1000;j++){
  for(var i=0;i<s;i++){
    setTimeout(moving($('#visitors>div')[i]), 1000, false);
  }
}
ここがカッコ悪いんで、出したくなかったんだけど…
外側の for文を while(1)とかにするとなんかオーバーフローしてしまうので、有限回のループです。
だから、ある程度時間が経つと止まります。