2005-07-31

Amazon最速検索β

アップしました。まだベータです。

http://la.ma.la/misc/aws/beta.html


早いうちにECS4に対応しようかと思ってたのですが、先にユーザーインターフェースのほうに色々手を加えました。ファイルサイズがやや大きくなってますが、ローカルにダウンロードしても動かしやすいように1ファイルにしました。

主な変更点
-作者、出版社で検索結果を辿れるようになった
-商品をドラッグで選択、まとめてカートに入れる機能
-カスタム表示機能
-検索結果を絞り込み
-Firefoxのクイックサーチに対応
-その他いろいろ。

Firefoxの「この検索にキーワードを設定」で登録できます。
こんな感じで。
http://la.ma.la/misc/demo/quicksearch.htm

なにげにこの機能、すごく便利だと思う。検索プラグインよりもこっちの方がいい気がする。というわけで、一応、検索結果にリンクを張ることが出来るようになってますが、betaなので将来的にクエリの書式は変わると思います。

カートに加える/まとめてカートに入れる機能は、Amazonのフォームを真似て作っても動くみたいですが、正式にサポートされている方法で実装しました。
http://aws.typepad.com/aws_jp/2004/12/ecs.html

ウィッシュリストに加えるフォームも付けてみました。後で買うのに便利ですが、紹介料が入らないのでおすすめできない。

カスタム表示機能は、とりあえず手をつけてみた感じでまだ中途半端。テンプレートを保存して共有したりする機能を付けて、将来的にはこれがメインになると思う。

----
技術的な解説

表示カスタマイズ用にText.QuickTemplateというのを書きました。
これを参考にして作った、単純な穴埋めだけのテンプレートエンジンです。
http://search.cpan.org/~roode/Text-QuickTemplate/

ビルトインオブジェクトをあれこれと拡張してます。

Function.later
String.fill
String.fill_expr
Array.each
Array.map
Array.loop
Object.extend
Object.each
Object.keys
Object.values
Object.loop
Number.times

これだけ加えました。

String.fillというのが肝で、QuickTemplateへのエイリアスみたいなものです。
こんな感じで使います。

"My name is {{name}}".fill({name:"John"}); //→ My name is John

"item".fill(item);
これだとidが「tmpl_item」の要素のinnerHTMLを拾ってきてマッピングします。
テンプレートはHTML中にコメントで埋め込んであって、比較的いじりやすいと思う。

toStringをオーバーライドすれば複雑なことも出来るので、実はあんまり高機能なテンプレートエンジンって必要ないような気がする。

JSANにアップとかしたいのだけれど、使い方がいまいちわからない。

実際にFunction.laterとか使ったりしてます。例えば連続で検索するのには最低1秒のウェイトが入るようになっていますが、do_request.later(500)(query)という具合に「500ミリ秒後に再試行」という動作を簡単に書けます。

prototype拡張セットは作っとくと何かと便利なのだけど、他のライブラリなんかとバッティングする可能性もあるので、標準セットみたいなのがあるといいのかなあ、とか。

とりあえず他の言語を真似て、必要そうなのを色々作ってみようかな、と思ってます。

Function.prototypeを拡張して遅延実行を実現する

JavaScriptにおいて関数というのはFunctionオブジェクトで、他のビルトインオブジェクトと同様に、組み込みのメソッドがある。これがapplyとcallしかないのだけれど、こんな感じに使う。

func.apply(thisObj,arguments)
func.call(thisObj,arg1,arg2,arg3)

thisObjには、その関数内で「this」として使うオブジェクトを指定する。applyの第二引数はargumentsオブジェクトを指定する。配列か、現在実行中の関数のargumentsオブジェクトを丸ごと別の関数に引き渡せる。つまり引数の長さが良くわかってなくても使える。

callは代わりに
func.apply(thisObj,[arg1,arg2,arg3])
と書けるので、実はいらないんじゃないかと思う。

これらは多分、ふつうにJavaScriptを書く上では全然必要でないし、知らなければ知らないでまったく問題にはならない。リファレンスなんかを見ても、リファレンス的な解説のみで具体的な使い方は書いていないと思う。

有名なのは引数の束縛とかそういうやつなのだけれど
http://www.interq.or.jp/student/exeal/dss/ejs/1/4.html

正直、これを読んでもどういうメリットがあるのかいまいち良くわからなかった。
でも、知っていれば知っているなりの書き方がある。

で、具体的に作ったのがこれ。
Object.extend = function(destination, source) {
    for (property in source) {
        if(source.hasOwnProperty(property)){
            destination[property] = source[property];
        }
    }
    return destination;
}
Object.prototype.extend = function(object) {
    return Object.extend.apply(this, [this, object]);
}
Function.prototype.extend({
    later : function(ms){
        var self = this;
        var func = function(){
            var arg = func.arguments;
            var apply_to = this;
            var later_func = function(){
                self.apply(apply_to,arg)
            };
            setTimeout(later_func,ms);
        };
        return func;
    }
});

var func = function(v){alert(v)};

func.later(1000)("1秒後に警告");
func.later(2000)("2秒後に警告");
func.later(1000).later(2000)("3秒後に警告");

(function(v){alert(v);arguments.callee.later(1000)(v)}).later(1000)("1秒ごとに実行")


Function.prototypeを拡張して、laterメソッドを加えてやる。加えるというのは正確ではなく、func.laterが見つからなかった場合に、Function.prototype.laterが実行されるようになる。これだけで、いちいち無名関数作ってsetTimeoutなんて書かなくても、あらゆる関数を簡単に遅延実行させることが出来るようになる。

Object.extendは便利だから使いまくる方向で。でもprototypeはコピーしないように改造した。
何をするコードかというと、元の関数を「1秒後に実行する関数」を生成して、返ってきた関数に引数を渡して実行する。この例だと引数は一つだけれど、複数の引数を必要とする関数でも同様に可能だ。
「1秒後に実行」ではないので、生成された関数をさらに加工することが出来る。三番目は1秒後に実行される関数をさらに2秒遅らせて実行する。

4番目はこれでもいい。
var interval = 1000;
(function(v){alert(v);arguments.callee.later(interval)(v)}).later(interval)("1秒ごとに実行");


実行中にinterval = 3000とかやったら実行中に外から周期を変更できる。arguments.calleeは現在実行されている関数の参照を表していて、これを使うと再起処理なんかをするのに関数名を決めうちにしなくて良い。

とまあ、こんな具合。

----
追記
later(1000).apply(this,arg)とかが動くように、ちょいと直した。
1秒後に別のオブジェクトに変形するオブジェクトとか作れる。

2005-07-29

Ajaxを使ったシンプルなチャット

を作ってみました。
http://la.ma.la/misc/ajaxchat/

サーバーサイドのソースは5行です。
http://la.ma.la/misc/ajaxchat/write.txt

CGIを使うのは書き込みの時だけで、表示はXMLHttpRequestを使ってログファイルを直接読み込みます。更新は差分取得で転送量とサーバー負荷を減らしています。

IEとFirefoxでしか動きません。
Operaはリクエストヘッダのセットが使えないので無理。

仕組み
-HEADリクエストでログファイルのサイズを取得
-ファイル末尾1KBを取得
-レスポンスヘッダから最終更新日と現在のファイルサイズを保存
-If-Modified-SinceとRangeヘッダをセットして定期リロード

とりあえずこれだけです。参加者表示も何もつけてませんが、改造の雛形にでもどうぞ。

Range付きのリクエストは昔試したときに、ヘッダをセットしても全部取得しちゃったりと、どうにもうまくいかなかった記憶があって試してなかったのだけど、なんかうまくいくようになってる様子。
こういうのを使えば負荷を極限まで減らせるわけで、カリカリにチューニングされたデータベースと、単純な追記式のログファイルだと、どちらが負荷が少ないのか気になるところ。

DATA[count++]={name:"名前",comment:"コメント1"}
DATA[count++]={name:"名前",comment:"コメント2",mail:"メール"}
DATA[count++]={name:"名前",comment:"コメント3"}

こんな感じにすれば、追記式で柔軟なデータ構造も保存できるんじゃないか、と考えていて、これだとJSONというかJavaScriptのコードって感じだけど。

----

Rangeリクエストを試したきっかけは、これなんだけど
http://ajaxdb.jp/

これ自体は微妙というかなんというか、Rangeリクエストはブラウザのキャッシュが利かないので、データベースとして使うならあらかじめファイルを分割しておくのが普通だと思う。

例えばこれ。
http://www.oldriver.org/jsmigemo/

pure JavaScriptによるmigemoで、あらかじめ分割しておいた辞書を動的にロードすることで実現しています。やればできるものだなあ、と関心。

----
追記

Operaは8.01からsetRequestHeaderが定義されていますが、実際に送信するヘッダに反映されないようです。8.02でも同上。8.02はなにやらPOSTメソッドの場合には反映される様子。

IEやFirefoxだと、自分で定義したヘッダを追加したり、HTTPで定義されてないメソッド使ったり、基本的に何でも出来るのですがRefererやAccpect-Encodingなんかは書き換えられなかったと思う。(あんま検証してない)

2005-07-27

ペイント暗号解読ツール

http://la.ma.la/misc/js/paint.html

これの簡易デコーダ。
http://semimaru.s47.xrea.com/blog/log/eid17.html

興をそぐので注意。
作成補助にも使えるかもしんない。


やじうまWatch経由
http://internet.watch.impress.co.jp/static/yajiuma/

2005-07-25

ホイール回転でAmazonのカートを少し便利にする

今、Amazon最速検索の改良バージョンを作っているのだが、Amazonはカートに入れるのは簡単なのにカートから削除するのとかがとても面倒くさい。そんなわけなのでホイール回転で注文数を変更できるようにするBookmarkletを書いてみた。

input type=textで、かつ、数値が入っている入力ボックス上でホイールを回転させると数値を変更できる。IEとFirefox兼用、他のブラウザはホイールを使えないので無理。ホイールマウスを持っていない人が切なくなるが、持ってる人が便利なのに越したことはない。

こんな感じで動く。
http://la.ma.la/misc/demo/amazonwheel.htm

Bookmarklet
ホイールで数量変更

Greasemonkeyスクリプトにしてみた。内容は同じ。
http://la.ma.la/misc/userjs/AmazonCartHelper.user.js

Amazonのカートを想定して作ったけれど、日付を入れるようなボックスとか、他にも何かと使い回しが利くはず。逆に言うと、こういう改良が利くので日付を入れるようなボックスはプルダウンメニューではなくて、普通のテキストボックスの方が良いと思う。

----
技術的な解説。
改行抜きで見づらいが、短いのでまあいいか。

javascript:(function(){var inp=document.getElementsByTagName("input");for(var i=0;i<inp.length;i++){var c=inp[i];if(c.type=="text"&&c.value!=""&&!isNaN(c.value)){var mkfunc=function(t){return function(e){var ct=e.wheelDelta||(e.preventDefault(),-e.detail);ct<0?t.value++:(t.value>0)?t.value--:0;return false}};var func=mkfunc(c);c.attachEvent?c.attachEvent("onmousewheel",func):c.addEventListener("DOMMouseScroll",func,false);}}})();


mkfuncが関数を返す関数で、現在のループ中で選択されているinputボックスへの参照を残している。例えば対象となるinput要素が10000個あったら別々の「無名関数」が10000個できる、といった具合。クロージャというらしいですが綴りがよくわからないので、いつも関数を作るということでmkfuncとかそういう名前を使ってます。

逆に、変数に「名前付きの」関数を代入してやるとうまくいかない。
javascript:(function(){var inp=document.getElementsByTagName("input");for(var i=0;i<inp.length;i++){var n=inp[i];if(n.type=="text"&&n.value!=""&&!isNaN(n.value)){var c=n;var func=function(e){var ct=e.wheelDelta||(e.preventDefault(),-e.detail);ct<0?c.value++:(c.value>0)?c.value--:0;return false};c.attachEvent?c.attachEvent("onmousewheel",func):c.addEventListener("DOMMouseScroll",func,false);}}})();

これは少し変わった動作をする。funcが「同じ関数」を参照してしまい、その関数内で使われている変数の参照先がループによって上書きされる。具体的には一番最後のinputボックスの数値が変化するようになる。

つまりこうなる。
http://la.ma.la/misc/demo/scopechain.htm

ちょっとした神秘だ。

別にクロージャを使わなくても同等のことは出来て、IE用のe.srcElementとFirefox用のe.targetを関数内で分岐させれば良い。

javascript:(function(){var inp=document.getElementsByTagName("input");for(var i=0;i<inp.length;i++){var c=inp[i];if(c.type=="text"&&c.value!=""&&!isNaN(c.value)){var func=function(e){var t=e.srcElement||e.target;var ct=e.wheelDelta||(e.preventDefault(),-e.detail);ct<0?t.value++:(t.value>0)?t.value--:0;return false};c.attachEvent?c.attachEvent("onmousewheel",func):c.addEventListener("DOMMouseScroll",func,false);}}})();


こっちの方が普通だし、メモリにもやさしい。ようするにクロージャとかカッコいい言葉を使ってみたかっただけだ。

----


まあ、こんなことはどうでも良くて、たかだか500byteでユーザビリティを劇的に改善することが出来るのだということをあなた方は知らなさ過ぎるのです。

2005-07-18

むしろdocument.writeの方を書き換えて遅延ロードを実現する

一つ前のエントリの続き。

document.writeを使うデメリットは一個前に書いたとおり、jsファイルを置いてあるサーバーのレスポンスに引きずられてページの描画が遅くなる、ということなんだけど

広告にせよRSS貼り付けにせよ、どれもこれもdocument.writeで表示するHTMLを書き出すものばかりなので、むしろdocument.writeの方を書き換えてみることにした。

ビフォー
http://la.ma.la/misc/js/feed2js_old.html

アフター
http://la.ma.la/misc/js/feed2js.html

Feed2JSというサービスを使っている。
http://jade.mcli.dist.maricopa.edu/feed/

本来は、Scriptタグの挿入位置にRSSやAtomフィードを貼り付けるサービスだ。前者が本来の使い方で、document.writeでHTMLが書き出されて、これをスタイルシートで装飾して使う。上の例では、ヘッダの次の位置に挿入してあるのでスクリプトのロードが完了するまで、フッターが描画できない。

後者は、document.writeを自前の関数で「上書き」して、いったんJavaScriptのオブジェクトに格納してからページに追加している。この方法であればスクリプトのロードが完了する前にページ全体を描画することが出来る。サーバーのレスポンスが遅れても、先にページ全体が描画されているので閲覧には支障が無い。scriptタグを動的に追加してやれば、ページの描画が完了した後からアイテムを追加することもできる。応用すれば完全なRSSリーダーも作れるだろう。

で、あんまり酷使するとサーバー負荷が気になるところだが、ミラーサイトもたくさんあるようなので、接続先を振り分けてやることもできるだろう。
http://jade.mcli.dist.maricopa.edu/feed/index.php?s=mirrors

これは結構無理やりな方法なので、本当はFeed2JSじゃなくてFeed2JSONが欲しいところなんだけども。

つまり何が言いたいかというと色んなサイトがRSSを吐いてくれて、RSSをJSONに変換するようなサービスがあって、そのミラーがたくさんあれば、余っている回線とCPUパワーを分散コンピューティング的に使って、サーバーの負荷を気にすることなくRemixだのなんだのに使うことが出来る。

という話。

2005-07-16

ページレンダリングを妨げない広告挿入手法に関する覚書

多分わかる人にはわかると思うんだけど、わかんない人にはわかんないと思うので、誰かわかりやすく書き直してくれると助かるんだけどとりあえず書いておく。

例えば、GoogleAdsenseなんかで広告を挿入するには、外部ドメインに置いたJavaScriptを読み込んで使う。セキュリティ上の問題があるにもかかわらず外部ドメインのJavaScriptをそのドメインにあるかのように取り込めるのは、多分、そういう需要があるからだろう。
んで、そういうスクリプトの構成というのは大抵、scriptタグを挿入した位置に広告がdocument.writeを使って書き出される、というものだ。

しかしこの方法には問題があって、それはscriptがロードされて実行が完了するまでページのレンダリングが止まる、ということだ。jsファイルを受信するまでにかかる時間と、解釈と実行、document.writeでブラウザに出力する、この間ブラウザはscriptタグを挿入した位置でHTMLをぶった切って表示できる範囲まで表示する、という動作をする。

例えば
-ヘッダ
-広告
-本文

というような順番でページを構成した場合、広告JavaScriptがロードされて、実行完了するまで5秒かかったら、本文が表示されないまま5秒間ユーザーは待たされることになる。

もし、
-ヘッダ
-本文
-サイドバー

というような後置型サイドバーの構成で、サイドバーに広告を入れる場合は、本文が先に表示されるので、ユーザーを待たせずに本文を読むことが出来る。広告JavaScriptのロードに5秒かかっても、とりあえず本文を先に読むことが出来る。

という違いがある。

GoogleAdsenseなんかをページに挟んでいる場合、その場所で若干レンダリングがもたつくのを体感したことのある人は多いはず、多いと思う。これはブラウザの正常な挙動である。scriptの実行が完了するまでページ全体がどういう構成になるのかわからないのだから仕方ない。

GoogleAdsenseの広告挿入スクリプトは、これだ。
http://pagead2.googlesyndication.com/pagead/show_ads.js

Googleの取っている手法は
-jsファイルは動的に生成しないで、共通のコードを使う。
-本体HTML側で指定したパラメータに応じてIFRAMEを生成する

つまり固定のJavaScriptで、動的に生成されたIFRAMEを埋め込んでいるわけだ。jsファイルに対してはブラウザのキャッシュが最大限利くようになる。とりあえず最速でjsファイルは渡しておいて、動的に生成されたIFRAMEが表示される。IFRAMEの中の表示が多少遅れても、ページのレンダリングには影響を与えずにすむ。
これはJavaScript自体を動的に生成するよりも、若干スマートである。JavaScript自体を動的に生成してしまうとブラウザのキャッシュが利かないのでページ毎に毎回ダウンロードする必要がある。

しかし、いずれにせよshow_ads.jsがキャッシュされていなかった場合は、jsファイルをダウンロードするまでに若干の時間がかかることになる。またIFRAMEを挿入するためにページのレンダリングはそこでいったん止まる。Googleのサーバーはレスポンスが速いので、気にしている人は少ないと思うけれど、もしこれが0.1秒でなくて1秒~2秒程度の遅延があるなら、明確に広告挿入はユーザーのストレスと結びつく。

これはテーブルレイアウトだとレンダリングが遅いとか、そういうのと同系統の問題なんだけど、あんまり触れられていないような気がする。外部サーバーに置いてある画像を直リンクで表示する場合は、サーバーのレスポンスが遅くてもとりあえずページ全体のレンダリングは出来るようになっている。しかし、外部サーバーに置いてあるスクリプトを取り込む場合、一番レスポンスが遅いサーバーにページの描画全体が引きずられることになる。

しかし、このページのレンダリングが遅れるという現象は、コードの書き方を少し工夫すれば解消することが出来る。

----
どうすればいいのか、というと。

広告表示用の領域を、あらかじめ固定の幅と高さで作っておいてやり、scriptのロードが完了したらその領域をスワップする、という方法であればjsファイルのロードが遅れたとしてもページのレンダリングを妨げることは無い。

例えば俺だったら

本体HTMLの側で
ADS_TARGET_ID = "任意のID"

広告JavaScriptの側で
document.getElementById(ADS_TARGET_ID).innerHTML = 広告HTML

というような作りにするだろう。
innerHTMLじゃなくてappendChildでもいいけど。
id=ADS_TARGET_IDの領域はスタイルシートを使ってあらかじめ表示される広告のサイズに合わせておけば、不要な再描画を避けることが出来るはずだ。
script要素にはdefer属性を指定しておく。defer属性が付いていれば、スクリプトのロードを待たずにページをレンダリングすることが出来る。

document.writeは確かにscriptの挿入位置が広告挿入位置でわかりやすいけれど、サーバーのレスポンスが遅ければ広告が挿入されているページの表示全てに支障をきたすので、そろそろやめにしてもいいんじゃないの、っていう。
これは広告だけの話ではなくて、例えばJavaScriptを使ってRSSのヘッドラインを貼り付けるようなサービスなんかでも同じことが言える。

まあ、そういう提案なんだけども、
具体的になんか作らないと説得力無いので、続く。

2005-07-12

prototype.jsのObject汚染を回避する方法

かなりターゲットの狭いTips。役に立たない。

prototype.jsというRuby on Railsなんかのフレームワークで使われている有名なJavaScriptのライブラリがあって、これが色々と使えそうな処理を綺麗に詰め込んであり、デファクトスタンダート的な地位を確立しているのだけれど、ちょっと微妙だなーと思うところがあって、それはObject.prototypeを拡張してしまう点。

実際の弊害はこういう。
http://d.hatena.ne.jp/nazoking/20050425/1114374966

要は連想配列として使うときに困るって話。
多分prototype.jsはJavaScriptの側でロジックを組むことをあまり想定していないため、この辺の問題にあんまり配慮していないのではないかと思うのだけれど、とりあえず無理やり回避する方法を思いついたので書いてみる。

http://la.ma.la/misc/js/prototype.html

IFRAMEを作ってやって、その中でObjectやArrayなんかを作成してやれば、何にも拡張されていない素の状態のオブジェクトを返すことが出来る、という具合。

どっちにしろ面倒くさいので微妙、使う機会があるのかどうか。
もっと真っ当な方法があるのかも知れない。

----
ついでだから後二つぐらいTipsを。

インストールされているフォント一覧取得(IE限定)
http://la.ma.la/misc/js/fontbrowse.html

バックグラウンドで一覧を勝手に収集して、フォントの普及率を調査するページとか作れそうだな、とか、考えたことがあった。


マウスのホイール操作を検知する(IEとFirefox限定)
http://la.ma.la/misc/js/wheel.html

あんまり使われてない気がするけど、JavaScriptでマウスのホイールイベントを拾ってくることが出来る。IEの場合はonmousewheel、Firefoxの場合はDOMMouseScrollというイベントハンドラが使える。IEとFirefoxで使えればカバー範囲は十分広いと思うので、もっと積極的に使っても良い気がする。


----
追記 07/12

一応書いておくと、これは「IFRAMEを使うことで別のJavaScript実行環境を作れる」という、いわゆるネタです。ビルトインオブジェクトをオーバーライドすると便利なんだけども影響が気になる、という時に使えるかもしれない。
上記のサンプルでも使っているけど、普通のやり方だとprototypeに含まれている要素を除外して処理する関数を作ったり、メソッドを加えてやれば良いと思う。

多分こんな感じ。
    Object.prototype.forEach = function(func){
        for(var key in this){
            if(!(key in this.constructor.prototype)){
                func(this[key],key,this)
            }else if(this[key] != this.constructor.prototype[key]){
                func(this[key],key,this)
            }
        }
    };
    obj.forEach(function(value,key,self){
        alert([value,key]);
    });

で、これをやるとますますObject.prototypeが汚染される上、
yomi["forEach"] = "ふぉーいーち"
という連想配列に対してはforEachメソッドが使えなくなるという罠もある。

自前のクラスを作るにしても全部Objectオブジェクトがベースになるので、個人的にはObjectはなるべくプレーンな状態で残しておいた方が望ましいと思う。
ただ、prototype.jsに関してはObject.extend()一つでコード量を大幅に減らせているメリットの方が大きいと思うので、その恩恵を受けつつもプレーンな状態のObjectを作成するにはIFRAME内で実行するぐらいしか思いつかなかった、という話。

----
さらに追記

プロパティが、そのインスタンスに固有なのか、クラスに元々存在しているのかを調べるにはhasOwnPropertyというのを使えば良いらしい。

http://www.interq.or.jp/student/exeal/dss/ref/jscript/object/objects.html#hasOwnProperty

上記のコードはこれで良さそう。
prototypeに含まれていない要素のみ回すforEach。
    Object.prototype.forEach = function(func){
        for(var key in this){
            if(this.hasOwnProperty(key)){
                func(this[key],key,this)
            }
        }
    };


オブジェクト指向良くわかってないので用語の使い方とか怪しいかもしれない。

2005-07-10

リアル・タイム・マシーン展の遅延マリオを再現するGreasemonkeyスクリプト

リアル・タイム・マシーン展
http://realtimemachine.dotimpac.to/

に、行ってきた。
内容を口で説明するのが難しいのでGreasemonkeyスクリプトを書いてみた。
Firefox専用。意味がわかる人がどれだけいるのか、という話ではある。

これをインストールして
http://la.ma.la/misc/userjs/DelayMaryo.user.js

ここへ
http://www.janis.or.jp/users/segabito/JavaScriptMaryo.html

マウスホイールを使って何が出来るか

あまり気が進まなかったのだが、サンプルとして適当だったので作ってみた。

Winkで作ったデモ。
http://la.ma.la/misc/demo/rssroll.htm

Greasemonkeyスクリプト、Firefox限定
http://la.ma.la/misc/userjs/HatenaRssRolling.user.js

デモ見てもわかりづらいが、見出しの部分の上でマウスホイールを回転させるとアイテムがロールする、クリックで開閉。マウスを動かさずに読むことが出来る、というもの。いまいちだったのでカーソル上下左右でも回転と開閉できるようにしてみた。
はてなは敵なので作りこむ予定無し。

Firefoxでマウスホイールにイベントを割り当てたときにスクロールをキャンセルする方法が全然わかんなかったのだが「event.preventDefault()」というのを使えば良いことがわかった。IEの場合の「event.returnValue=false」に相当するようだ。DOMMouseScrollにしてもそうだが、preventDefaultについても検索結果があまりに少ないような気がする。

というわけで、JavaScriptでイベントをキャンセルするサンプル
つるつるして気持ち悪いページを作ることが出来る。
http://la.ma.la/misc/js/cancel.html

このあたりはブラウザごとに癖があるので使いどころが難しいのだが、Firefoxだとキーボードの操作を何でもキャンセルできるっぽいのでブラウザ上で完全なキーバインド割り当てが出来そうだ。F5リロードなんかも無効にすることができる。XULアプリを作ることを考えると、完全にコントロール出来ないとおかしいわけだけど普通のHTML上でもできる。つまりCtrl+Sでページを保存するWikiとか作れそうだ。

Sleipnirなんかの場合は、ブラウザ本体のショートカットが何よりも優先されるので、Ctrl+Sの上書きは無理。

2005-07-07

日本語テキストをテーブルで表示するJavaScriptの高速化

いやなブログ: 日本語テキストをテーブルで表示する
http://namazu.org/~satoru/blog/archives/000039.html

を高速化してみた。
http://la.ma.la/misc/js/tablefont.html

オリジナルは多分、あえてDOMでテーブルを作っていると思うのだけれど、innerHTMLを使って書き直してみた。IEで6倍ぐらい、Firefoxで4倍、Operaで3倍速い。

ちなみに手元の環境では
Opera8 : 30ミリ秒
Firefox : 100ミリ秒
IE : 360ミリ秒
ぐらいになった。

Operaは7までJavaScriptやDOM周りの操作が遅い遅いと言われていたのだけれど、Opera8になって何だか妙なチューニングが施されている。
今はOperaは何かと癖があって扱いづらいのだけど、ブラウザベースのリッチクライアントが普及してくるとJavaScriptの実行速度がブラウザのシェアに影響を与えたりするのかもしれない。

JavaScriptの高速化には色々とテクニックがあるので、そのうちチューニングガイドでも書こうかな、と思ってる。
DOMとinnerHTMLのパフォーマンスの違いについてはここら辺に詳しい。
http://www.quirksmode.org/dom/innerhtml.html

2005-07-05

IEにFirefox風検索バーをくっつけるBookmarklet

というのを作ってみました。
このページで試してみる(IE限定)

ローマ字のまま検索できるので、例えばmohicanでモヒカンにヒットします。
例えばこんな具合。
http://la.ma.la/misc/demo/isearch.htm

上記ブックマークレットで他のページでも動かせますが、リファラが付くとイヤなのでやめてください。いきなりクッキー盗んだりするスクリプトに化ける可能性があるので、ローカルに保存して動作させることを推奨します。

で、ファイル保存してJavaScript書き換えてリンクバーに登録とかいう作業は非常に面倒くさいと思うので、ブックマークレットで使うjsファイルの中にWSHを使ったインストーラーを書いてみた。

----
インストール方法
http://la.ma.la/misc/bookmarklet/isearch.js
このファイルを適当なフォルダに保存してダブルクリックで実行。インストーラーが起動するようになってます。XPSP2だと警告出ると思いますがOKで。信用ならない場合はソース確認して実行してください。Bookmarkletをクリップボードにコピーと、IEのリンクバーに追加ができます。

Sleipnir用スクリプトも作ってみました。
http://la.ma.la/misc/bookmarklet/sleipnir_isearch.zip

sleipnir\user\ユーザー名\scriptsフォルダに解凍してください。
「ツール」に登録しておけば、ショートカットキーやマウスジェスチャで起動できます。
試しにCtrl+Fを標準のダイアログと置き換えてみましたが、Firefoxもどきという感じで割と快適に使えます。ただ、JavaScriptを有効にしてないと使えないので、ちょっと困るかも。

----
オリジナルはこれです。
http://d.hatena.ne.jp/leibniz/20050701/1120232028

ライセンスはオリジナルにしたがってArtistic Licenseとします。
フレームまたがって検索できなかったり、何かと不具合あります。
動作無保証、自己責任で使ってください、という感じで。

JavaScriptでここまでできるのか!という感じのデモではあるのですが、IEの標準のページ内検索機能も内部的にはJavaScriptで実装されているので、実はあんまりすごくないかも。