uu.node.add(source, context, position) のpositionに数値を指定可能に

uu.node.add(source, context, position) の position に数値を指定すると、ノードの挿入位置を指定可能になりました。従来通り文字列("first", "prev", "./first", "./last", "next", "last")も指定可能です。

詳しくはこちら http://code.google.com/p/uupaa-js/wiki/uu_node_add

<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8" />
<title>uu.node.add(NodeArray)</title>
<style>
.div1 { background-color: #111; color: white; }
.div2 { background-color: #333; color: white; }
.div3 { background-color: #555; color: white; }
#contextNode { background-color: blue; }
</style>
<script src="../../src/uupaa.js"></script>
<script>
uu.ready(function(uu, doc) {
    var nodeArray = [uu.div({ "class": "div1" }, "div1"),
                     uu.div({ "class": "div2" }, "div2"),
                     uu.div({ "class": "div3" }, "div3")];
    var ctx = uu.id("contextNode");

    var pos = [
        "first", "prev", "./first", "./last", "next", "last"
    ];

//    uu.add(nodeArray, ctx, pos[3]);
    uu.add(nodeArray, ctx, 4);
});
</script>
</head><body>

<div id="parentNode">

    <div id="firstSibling">    first(0)   </div>
    <div id="prevSibling">     prev(1)    </div>
    <ul id="contextNode">(2)
        <li id="firstChild">  ./first(.0) </li>
        <li>(.1)</li>
        <li id="lastChild">   ./last(.2)  </li>
    </ul>
    <div id="nextSibling">     next(3)    </div>
    <div id="lastSibling">     last(4)    </div>

</div>

</body></html>

速度が求められる部分に Function#bind を導入するのはまだやめましょう

Function#bind は、ECMAScript-262 5th で導入された Prototype.js 由来のメソッドです。
uupaa.js では Function#bind の互換実装も提供していますが、現在はコメントアウトした状態でリリースしています

Function#bind と 自分でbind相当の処理を書いた場合のベンチマークhttp://jsdo.it/uupaa/FunctionBind に載せました(引数を束縛する使い方についてのベンチマークです)。

Browser ratio Function#bind 自分でbind loops
Chrome 9 dev 335% 171 51 *100
Firefox 4 beta 9 93% 298 320 *100
Firefox 3.6 ---- ---- 764 *100
IE 8 824% 1590 193 *10
IE 9pp7 84% 112 134 *100
Opera 11 1897% 1802 95 *100
--------------------------------
HTC Desire HD (Android 2.2) 1844% 166 9 *1
IS06 (Android 2.2.1) 816% 204 25 *1
iPhone 3GS (iOS 4.2.1) 992% 258 26 *1
Android Dev Phone 1 (Android 1.6) 1685% 1415 84 *1

HTC Desire HD - 1GHz, 768MB
IS06 - 1GHz, 400MB
iPhone 3GS - 600MHz, 256MB
Dev Phone 1 - 528MHz(384MHz), 192MB

Android は一見フリーズしたような状態になりますが、暫くすると画面が更新されます。
ベンチマークスクリプトAndroidの場合にloop回数を考慮していなかったため修正し、再測定後のスコアに差し替えました。

Debug friendly な ON/OFF できるロールオーバー機能の実装

SubmitボタンのonClickイベントの流れを追いたいのに、Submitボタンに仕掛けられたロールオーバー機能(mouseover等)にデバッガの制御を奪われて、しかもその先がMinifyされたライブラリで、イラッとしたことはありませんか?

デバッグ中はロールオーバーを一括でOFFにして、あとで戻せればいいじゃない? と思い、そのような機能を作ってみました。

jsdo.it で試せます http://jsdo.it/uupaa/DebugFriendlyRollover

<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8" />
<title></title>
<style>
.rollover {
    outline: 5px solid green;
}
</style>
<script src="src/uupaa.js"></script>
<script>

uu.ready(function(uu, doc) {
    uu.add([
        uu.click(uu.button("rollover target button1"), fire),
        uu.click(uu.button("rollover target button2"), fire),
        uu.click(uu.button("rollover target button3"), fire),
        uu.click(uu.button("rollover target button4"), fire)
    ]);

    uu.rollover(uu.query("button"), "rollover");
});

function fire(evt) {
    alert("onclick");
}

function handleClick(evt, off) {
    if (off) {
        uu.rollover.disable();
        alert('ロールオーバーをOFFにしました')
    } else {
        uu.rollover.enable();
        alert('ロールオーバーをONにしました')
    }
}

(function(uu) {

// build struct
uu.rollover = uu.mix(uurollover, {
    enable:     uurolloverrebind.bind(null, true), // Function#bind(null, true)
    disable:    uurolloverrebind.bind(null, false) // Function#bind(null, false)
});
uu.unrollover = unrollover;


// uu.rollover - bind rollover handler
function uurollover(node,   // @param NodeArray/Node:
                    roll) { // @param ClassNameString/CallbackFunction:
    var db = uurollover._,
        ary = node.nodeType ? [node] : node, // toArray
        i = 0, iz = ary.length;

    for (; i < iz; ++i) {
        uu.event.hover(ary[i], roll, "rollover"); // hint("rollover")
        db.node.push(ary[i]);
        db.roll.push(roll);
        db.disable.push(false);
    }
}

// uu.unrollover - unbind rollover handler
function unrollover(node) { // @param NodeArray/Node(= void):
    var db = uurollover._,
        ary = node.nodeType ? [node] : node, // toArray
        i = 0, iz = ary.length, pos;

    for (; i < iz; ++i) {
        pos = db.node.indexOf(ary[i]);
        if (pos >= 0) {
            uu.event.unhover(ary[i]);
            db.node.splice(pos, 1); // remove
            db.roll.splice(pos, 1);
            db.disable.splice(pos, 1);
        }
    }
}

// inner - uu.rollover.rebind
function uurolloverrebind(enable) { // @param Boolean: true -> hover, false -> unhover
    var db = uurollover._,
        ary = db.node,
        i = 0, iz = ary.length, j, jz,
        fn = enable ? uu.event.hover : uu.event.unhover;

    for (; i < iz; ++i) {
        if (enable === db.disable[i]) {
            fn(ary[i], db.roll[i], "rollover");
            db.disable[i] = !enable;
        }
    }
}

// db
uurollover._ = {
    node: [],   // Node:
    roll: [],   // ClassNameString/CallbackFunction:
    disable: [] // Boolean:
};

})(uu);
</script>
</head><body>
<p>debug friendly な ON/OFF できるロールオーバー機能の実装</p>

<input type="button" value="rollover off()" onclick="handleClick(event, true)" />
<input type="button" value="rollover on()" onclick="handleClick(event, false)" />
<hr />

</body></html>

rollover がデバッグのジャマになるときは、コンソールに

uu.rollover.disable()

とタイプしてください。
uu.rollover を使って仕掛けた mouseover ハンドラなどが一括で外れます。


uupaa.js 0.8 上では uu.rollover → uu.event.rollover となる予定です

地獄のJavaScript (Symbolic JavaScript)

jsdo.it に投稿してみた http://jsdo.it/uupaa/hellsjs

誰かうまいこと、a〜zをつくりだしてほしいんだよ。

uu.ready("window", function(uu, doc) {
  uu.mix(window, uu);
  uu.config.log.rollup = 1000; // logロールアップ回避

  log('-1 = @',   ~[] );  
  log('0  = @',   +[] ); // or -[]
  log('1  = @',  -~[] ); // or +!+[] 
  log('2  = @',  -~[] << -~[] ); // 1<<1
  log('3  = @',  -~[]  + -~[] + -~[] ); // 1+1+1
  log('4  = @',  -~[] << -~[] + -~[] ); // 1<<1+1
  log('5  = @', (-~[] << -~[] + -~[]) +
                                -~[] ); // (1<<1+1)+1
  log('6  = @', (-~[]  + -~[] + -~[]) <<
                                -~[] ); // (1+1+1)<<1
  log('7  = @', ((-~[] + -~[]) <<
                 (-~[] + -~[])) -
                         -~[] );   // (1+1)<<(1+1)-1
  log('8  = @', ((-~[] + -~[]) <<
                 (-~[] + -~[])) ); // (1+1)<<(1+1)
  log('9  = @', ((-~[] + -~[]) <<
                 (-~[] + -~[])) +
                         -~[] );   // (1+1)<<(1+1)+1
  log('-2147483648 = @',   ~[] << ~[]  );
  log('2147483647  = @', ~(~[] << ~[]) );

  log('true  = @', !![] ); // or  !+[]
  log('false = @',  ![] ); // or !!+[]
  
  log('"t"   = @', (!![]+"")[    +[]  ] );  
  log('"r"   = @', (!![]+"")[   -~[]  ] );  
  log('"u"   = @', (!![]+"")[  (-~[]) + (-~[]) ] );  
  log('"e"   = @', (!![]+"")[  (-~[]) + (-~[]) +
                                        (-~[]) ] );
  log('"f"   = @', (![]+"")[   +[]  ] );  
  log('"a"   = @', (![]+"")[  -~[]  ] );  
  log('"l"   = @', (![]+"")[ (-~[]) + (-~[]) ] );  
  log('"s"   = @', (![]+"")[ (-~[]) + (-~[]) +
                                      (-~[]) ] );
  log('"e"   = @', (![]+"")[ (-~[]) + (-~[]) +
                             (-~[]) + (-~[]) ] );

});

jsのオレオレ演算子

jsのオレオレ演算子といえば、

  • Boolean値に変換する !!arg
  • 0又は1に変換する +!!arg
  • 小数点を切り落とす arg|0 や arg >> 0

などがありますが、若干分かりづらいので、初心者の方にはおすすめできません。

http://jsdo.it/uupaa/9YFT で実行できます。

uu.ready(function(uu, doc) {
  var arg = 1.23;

  uu.log(" !!arg = @", !!arg);
  uu.log("+!!arg = @", +!!arg);
  uu.log(" arg|0 = @", arg|0);
  uu.log("arg>>0 = @", arg>>0);
});

uu.ready("href:url/dispatcher", callback, ...) impl

条件成立でコールバックする関数を登録しておく機能 uu.ready() の条件に、ページのURLが一致する場合にコールバックする機能を追加しました。

このようにして利用します。同じものを http://jsdo.it/uupaa/2011-01-19 にあげてあります。

// jsdo.it のコードは <iframe> 内で動作するため、
// DOMContentLoaded の発火タイミングが <html> 内に設置した場合と
// 異なります。ご注意ください。
<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8" />
<script src="src/uupaa.js"></script>
<script>
uu.ready(function() {
  alert("DOMContentLoaded - this location.href = " + location.href);
});

uu.ready("href:/http://jsdo.it/nguser", function(uu, doc) {
    // この関数は指定されたURLにマッチしないためコールバックされません
    alert("NG - http://jsdo.it/nguser...");
  
}, /* 条件を省略 */ function() {
    // この関数は指定されたURLにマッチしないためコールバックされません
    // uu.ready(callback, callback, ...)のように関数を連続して記述すると、
    // 直前の条件の評価に基づいて関数を連続でコールバックします
    alert("NG - http://jsdo.it/nguser...");
  
}, "href:uupaa/2011-01-19", function(uu, doc) {
    // 最初にコールバックされます。DOMContentLoaded前に呼ばれる可能性があります
    alert("OK1 - uupaa/2011-01-19...");
  
}, "href:uupaa/2011-01-19", function(uu, doc) {
    // 2番目にコールバックされます。DOMContentLoaded前に呼ばれる可能性があります
    alert("OK2 - uupaa/2011-01-19...");
  
}, /* 条件を省略 */ function() {
    // 条件が省略されているため、直前の条件の評価に基づき、
    // この関数は3番目にコールバックされます
    alert("OK3 - uupaa/2011-01-19...");
  
}, "dom", function(uu, doc) {
    // DOMContentLoaded のタイミングでコールバックされます。
    alert("DOMReady(DOMContentLoaded)");
  
}, "window", function(uu, doc) {
    // window.onload のタイミングでコールバックされます。
    alert("WindowReady(window.onload)");
  
}, "storage", function(uu, storage) {
    // WebStorage(または互換機能)が利用可能になったタイミングでコールバックされます。
    alert("StorageReady");

}, "audio", function(uu, doc) {
    // <audio>(または互換機能)が利用可能になったタイミングでコールバックされます。
    alert("AudioReady");
  
}, "canvas", function(uu) {
    // <canvas>(または互換機能)が利用可能になったタイミングでコールバックされます。
    alert("CanvasReady");

}, "svg", function(uu) {
    // <svg>(または互換機能)が利用可能になったタイミングでコールバックされます。
    alert("SVGReady");
});
</script></head><body></body></html>

クリスマスツリーを iPhone でも見れるようにしたよ


ジェバンニ仕事で、http://koebu.com/event/xmas/2010/iPhone / iPad に対応させました*1

目からビーム出して頑張ってる最中のツイートまとめ

iOS では、ユーザアクションを伴わない、audio.play() やaudio.load() は機能しない。さらにpreloadとautoplay属性も無効化されている http://bit.ly/fmLf5y in Device-Specific Considerations
http://twitter.com/uupaa/status/15433471463784449

なんかね、PCブラウザのように、audio.play() 呼んでも audio.load() 呼んでも動かなくて、色々ググッたんだよね。

iOS でBGMは、そのままでは自動再生できないので、ユーザに画面をタッチしてもらう必要がある
http://twitter.com/uupaa/status/15433728247468035

この時点では、window.addEventListener("touchstart", function() {...}, false) でイベントかすめとって、その隙に色々やったらどうなんだろう? とかモヤモヤしてた

つまりiOSでは、なんとしても画面にタッチしてもらって、そのタッチイベントハンドラ内で、audio.load()を含めゴニョゴニョまとめてやってしまって、ある程度までオーディオデータの読み込みが進んだら(canplayイベントが発生したら)、play()で再生すればできるのかな?
http://twitter.com/uupaa/status/15434696645156864

いやいやいやいやいや… ソレ無理あるだろうと。この後思い直すわけですが。

クリスマスツリーをiPhone対応にするためにゴリゴリ書き直してる。
http://twitter.com/uupaa/status/15439252250697728

寂しさと空腹に負けてツイート

iOS

ドーパミンドバ〜 した時のつぶやき

そもそも状態遷移が違う

クリスマスツリーのページを、PCブラウザ向けに組んだ時は、

  1. mp3ファイルの一覧をAPIで取得する
  2. 取得したmp3ファイルを裏で読み込み、再生可能な状態になったらプレゼントとして登場させる
  3. クリックで再生開始

といった流れで、状態を管理していたのですが、
Apple の技術資料 http://bit.ly/fmLf5y にも書かれているとおり、ユーザアクションを伴わない形で audio.play() や audio.load() を実行しても iOS では機能しないから、PC用サイトのUIを組む感覚でiPhone用サイト組むと全くダメなことが判明。

モバイルSafariで動かすために、

  1. mp3ファイルの一覧をAPIで取得する
  2. プレゼントとして登場させる
  3. クリックでaudio.load()を実行 canplayイベントを待ってaudio.play()で再生開始

という別の流れを作成する必要がありました。
また、AudioPlayerクラスをPCブラウザ用(XmasPlayer)とは別に用意(XmasMobilePlayer)することで、モバイルSafariでの音声の再生も可能になりました。
# uupaa.js にはOOP的なクラスを作る機能があります。

モッサリをサクサクにする(アニメーションを軽くする)

クリスマスツリーのページのプレゼントボックスは、uupaa.js の uu.fx() を使いアニメーションをさせています。

uu.fx() の本質的なコードは以下のように、style.left と style.top といったスタイル属性を定期的に書き換えるというものです。

function tick() {
  node.style.left = x + "px";
  node.style.top  = y + "px";
}
setInteval(tick, 12);

ただ、left や top をタイマーで書き換える方式では、雪を4つ降らせただけでモッサリになってしまい、とても人前に出せる状態にはありませんでした。
そこで、前々からやりたかった機能(CSS3 Transition) を uu.fx() に組込み、機能強化を行いました*2

http://code.google.com/p/uupaa-js/source/detail?r=994

uu.fx.moveIn(), uu.fx.shlink(), uu.fx.flare(), uu.fx.puff() で、モバイルWebKitなら WebKitTransform を利用するように
uu.fx(node, duration, { tx, ty }) を指定時に、モバイルWebKitなら WebKitTransform を利用するように

従来は、

  • uu.fx(node, duration, { x:100, y:100 }) や
  • uu.fx(node, duration, { left:100, top:100 }) で

アニメーションを作成しますが、
これからは、

  • uu.fx(node, duration, { tx:100, tx:100 }) と

するだけで、uupaa.js が動作環境を判断し、PCブラウザなら tx → left と解釈、MobileSafari なら tx → WebKitTransform:transitionX と解釈するようになります。

つまり、

// これまで
uu.fx(node, duration, uu.env.mobile ? { transitionX:100, transitionY:100 } : { left:100, top:100 })

   ↓↓↓

// これから
uu.fx(node, duration, { tx:100, tx:100 })

スッキリ!

あっそうそう

IE やモバイルSafariで見るとクリスマスツリーが短くて、その他のブラウザだと長かったり、複数の音声を同時に再生してガヤガヤできたりします。
あと、MobileSafariの<audio>は、ボリュームいじれないみたいなので、声が聞こえない時は iPod アプリを起動して音量大きくすると聞こえるようになります。

Boy Meets Girl !!

*1:iOS4.2 以上推奨です。OSが古いと <audio> 動かないかも

*2:この辺は改めて別のエントリで説明します