正規表現を使わないCSS3対応の「属性限定」セレクタ関数

category-icon

 ppBlogでは、不十分ですけどCSS3対応のセレクター関数を実装していたんですが、これを大幅に書き換えました。従来は、XPathが使えるブラウザであれば、CSS3のセレクタをXPath用に変換して、意中の要素を取り出したりしていました(スピード面でなかり有利)。でも、document.querySelectorAllをサポートするブラウザが当たり前になってきたので、少なくともppBlogでは、XPathは不要という結論に達しました(今やFirefoxのためだけに用意してる感じで、しかも次期v3.5では間違いなくquerySelectorAllがサポートされるでしょうから)。

 ppBlogで使っている要素抽出のためのセレクターは、大体以下のような感じです。

  • o("#ID") 系 // IDを持つ要素を取り出す
  • o(".className") 系 // 特定のクラス名を持つ要素を取り出す
  • o("a[rel=edit]") // 属性セレクター系。A要素でrel="edit"のやつを取り出す
  • o("textarea[id^=Page]") // 属性セレクター系。テキストエリア要素でID名がPageで始まる要素を取り出す

とまぁ、こんな感じで、ほぼ頻度順になってて、圧倒的にID名抽出が多いです。なので、別に高機能なセレクター関数は必要ないわけで。XPathへの変換にえらくファイル容量を割いていたんですが、それをばっさり削ったんで、だいぶシェイプアップされました(oParts.js)。

 ただ、属性セレクター(上のリストの下2つ)は、今後、色々使い道がありそうなので、これはスピードも考慮した関数を実装しました。以下のような関数です。正規表現を使っていないので、速度面で有利です。面白いことに、FirefoxよりIE6-8の方が高速に動きます。別に詳しく計測した訳ではありませんが、属性セレクターに限定すれば最速の部類ではないかと思っています(XPathやquerySelectorAllを使わないレガシーな系で)。

// cache = {}; // キャッシュ使うなら
function queryByAttributeSelector(rule){
 var apos = rule.indexOf('[');
 var tag = rule.slice(0, apos); // タグ名を抽出。
 var attr = rule.slice(apos + 1, -1); // 属性を抽出
 attr = (attr.indexOf('][') > 0) ? attr.split('][') : [attr];
 var s = '', e, k, ks, v, z, i = 0, l = attr.length;
 // if(!cache[rule]){ // キャッシュ使うなら
 for(; a = attr[i++];){
  e = (i != l ? ' && ' : '');
  z = a.split('=');
  if(z[1]){
   k = z[0], ks = k.slice(-1);
   v = z[1].indexOf('"')===0 ? z[1].slice(1, -1) : z[1];
   k = k.indexOf('class') !== -1 ? k.replace('class', 'className') : k;
   switch (ks){
    case '^' : k = k.slice(0, -1); s += 'n["'+k+'"].indexOf("'+v+'")===0' + e; break;
    case '$' : k = k.slice(0, -1); s += 'n["'+k+'"].lastIndexOf("'+v+'")===(n["'+k+'"].length - "'+v+'".length)' + e; break;
    case '~' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+' ")===0 || n["'+k+'"].indexOf(" '+v+' ")!==-1 || ((z=n["'+k+'"].indexOf(" '+v+'"))>0 && !n["'+k+'"].charAt(z+"'+v+'".length+1)))' + e; break;
    case '*' : k = k.slice(0, -1); s += 'n["'+k+'"] && n["'+k+'"].indexOf("'+v+'")!=-1' + e; break;
    case '|' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+'-")!= -1)' + e; break;
    default  : s += 'n["'+k+'"]=="'+ v +'"' + e;
   }
  } else {
   s += 'n["'+(a==='class'?'className':a)+'"]' + e;
  }
 }
 /*cache[rule] = s;   // キャッシュ使うなら
 } else {
  s = cache[rule];
 }*/
 var r = [];
 var items = document.getElementsByTagName(tag||'*'), j = 0, n;
 var v = new Function('n', 'return ('+s+')');
 for (;n = items[j++];){
  if(v(n)){
   r[r.length] = n;
  }
 }
 return r;
}

 工夫した点は、indexOfやらlastIndexOfの積極的活用です。split関数は、正規表現を用いない文字列での分割は、用いる場合より何倍も高速です。後、細かい速度の改善については、uupaaさんLinkエントリLink [latest log]が非常に参考になりました。

CSS3の「属性」セレクターをほぼ網羅していると思いますが、とりあえずhttp://mootools.net/slickspeed/Link にある属性セレクターはすべてパスします(ただし、div[class!=made_up]は、CSS3にはなさそうなので未対応)。

 上のソースコメントをみれば分かりますが、キャッシュを有効にするとより速くなります。でも別になくても良いかなと思ってます。細かい点を言えば、A要素のREL属性なんてのは、大文字小文字を区別しないようですが、そこまでは追ってません。要は、ppBlogで動けば良いので。

 ppBlog1.8.0での実装は、上のソースをベースにしたものになっています。

— posted by martin at 09:11 pm   commentComment [0]  pingTrackBack [0]

テキストエリアの自動リサイズ再び

category-icon

 以前、テキストエリアのサイズを文章の長さに合わせて変えるスクリプトを考えて、それをppBlogのエディタにも採り入れているのですが、それは意図した様に動くには動くのですが、えらく力業なスクリプトでした。その恥ずかしいスクリプトは、ここLink に置いてます?;w)

 記述も長くてスマートでないし、もっと簡単に出来ないかなぁと考えてみましたが、要は、自動調節というのは、テキストエリアのスクロールバーが表示されなくなるまでエリアを伸ばすということなので、その考えで行けば簡単でした。以下のような記述でいけますね。

function autofit(el){
 if(el.scrollHeight > el.offsetHeight){
  el.style.height = el.scrollHeight + 'px';
 } 
}

element.scrollHeightで、目的のテキストエリアの、スクロール分も含めた高さ(+ボーダー幅)を取得できます。で、element.offsetHeightで、見た目の高さを取得できます。なので、スクロールバーが表示されている状態というのは、文章が長くて表示しきれない状態なので、じぁ、テキストエリアの高さをscrollHeightに合わせれば良い、ということになりますね。なんで、こんな簡単なことに気が付かなかったかなぁ。まず、このデモを見てみましょう。


 とりあえずは、これでよさげですが、もうちょっといじってみましょう。例えば、この文章をコピペして、元の文章の下に貼り付けると、表示領域からはみ出るので、自動的にスクロールバーが表示されると思います。なので、ここでもう一度「AutoFit」ボタンを押すと、テキストエリアが広がります。次に、この貼り付けた文章を削除してみて下さい。テキストエリアが広がった分、下に大きな余白が出来ますね。今度は縮めようと再度ボタンを押してもテキストエリアのサイズは変わりません。これは、このautofit関数の定義からして当然なのですが、気持ちとしては、今度は逆に縮んで欲しいなと。では、どうするか?

 強制的にスクロールバーを表示させる状態に持っていって、そこでこのautofit関数をかませば良いです。スクロールバーを表示させるには、意図的にテキストエリアの縦サイズを小さく取ればよいわけです。なので、極端な話、

element.style.height = '1em';

とでもして、つぎにautofit()を呼び出せばOKですが、これだと見た目にテキストエリアがスプラッシュみたくなるので、なるべく最小量のサイズ変更でいくには、次のような感じで良いかなと思います。

/* AutoFit関数その2 */
function autofit2(el){
 if(el.scrollHeight > el.offsetHeight){
  el.style.height = el.scrollHeight + 'px';
 } else {
  while (el.scrollHeight - 50 < parseInt(el.style.height)){
   el.style.height = parseInt(el.style.height) - 50 + 'px'; // 適当に50pxずつ
  }
  arguments.callee(el); // 高さを縮めてスクロールバーが出てくるタイミングで呼び出す
 }
 el.focus();
}

 以下に、これを適用したものを付けておきます。上の関数との動作の違いが分かると思います。


 この関数をonkeyupなどのキーイベントと結びつけておけば、入力に合わせて自動的にサイズが変わるテキストエリアの出来上がりです。これのデモを以下に挙げておきます。

 →http://p2b.jp/demo/autofit-textarea.htmlLink


— posted by martin at 05:30 am   commentComment [2]  pingTrackBack [0]

IE8はGoogle Chrome、Firefoxよりも高速って

category-icon

 ITmediaより。IE 8はGoogle Chrome、Firefoxよりも高速――MSが独自テストの結果を発表Link

 タイトルだけ見ると、果たしてJavaScriptの話なのか(まぁ、これはないと思うけれど)、それともレンダリングの話なのかと思ったけれど、どうやらページの読み込み速度の話みたい。ページ読み込み速度って、レンダリング速度とは別物なのだろうかと思いつつ、確かに、ローカルな環境で、主要なブラウザを使ってppBlogの動作チェックしていると、IE8のレンダリングが速くて、思わず「ほう」とうなってしまうことが多々ある。いつぞやのOperaを彷彿とさせる。でも、まだ、細かいところで、おやっと思う動作があるんだけれど。

— posted by martin at 10:38 pm   commentComment [0]  pingTrackBack [0]

コメント読み込み時にスクロール

category-icon

 ppBlogでは、コメントやトラックバックが付いている記事には、それを記事の下の方に表示するレスポンス展開ボタンが付いてます(複数記事モードのとき。単独表示では、最初からコメント類は表示されている)。ここで、ボタンをクリックすると、Ajax経由で動的にコメントを読み込むので、わざわざページをリロードさせる必要もなくスムーズにコメントなどを読むことができます。

 現行では、このボタンをクリックしてデータを読み込むと、コメントエリアにフォーカスが移っていたと思いますが(ウィンドウがスクロールする)、これが、瞬時でピクッと移るために、何となく読み手の視点が混乱するというか、一瞬、何が起こったのか分からなくなるかもしれないと思い、このフォーカス移動を止めてみました。

 すると、特にウィンドウのスクロールを伴うことなく、展開されたコメント・トラックバックがボタンの下に表示されて良い感じです。でも、ひとつ問題があって、もし、このレスポンス展開ボタンをブラウザ画面の結構下の方でクリックすると、展開されたコメント類がブラウザ画面の領域下に表示され、これはこれで、何も展開されてないように見えるので、読み手を困惑させる可能性があります。

 なので、Ajaxで読み込んだデータを展開した際に、それらがウィンドウの枠外であるときに限って、そのエリアまでスクロールさせるようにしました。かといって、単なるフォーカスの瞬間移動では、最初に述べたように、これも読み手を惑わせるかもしれないので、瞬時に移動ではなくて、スクロール制御するようにしてみました。以下のようなスクリプトでいけます。

var wscY = oParts.metrics(3); // ウインドウのスクロール量を取得
var firstComment = o("div.comment-entry", commentsDiv).item(0); // 表示エリアの最初のコメント要素を取得
var diff = firstComment.offset(1) - Number(wscY + oParts.metrics(1)); // 上記コメントを表示させるためのスクロール量を算出

if(diff > 0){
   (function(){
     var a = 30, s = arguments[0] / a; // スクロール量を適当に分割。ここでは30分割してる 
     var yy = []; // 縦移動させるスクロールポイントを入れる配列を用意
     for (var i = 0; i < a; i++) yy[i] = wscY + s * i; // 適当に移動ポイントを作成
     var i = 0, t = setInterval(function(){ window.scrollTo(0, yy[i++]); if(i >= a) clearInterval(t);}, 1);
   }).await(300)(diff + firstComment.rect(1) + 140); // データ読み込み後、300ミリ秒後に発動
  }
});

 データを読み込んでからいきなりスクロールしだすと、それで面食らうかもしれないので、.await(300) と微妙に間を持たせています。

 ここのサイトで、実際にコメントの付いた記事で試せますので、興味のある方は、あえてコメント展開ボタンをブラウザ画面ぎりぎりにセットして試して下さい。

— posted by martin at 02:45 am   commentComment [0]  pingTrackBack [0]

ChromeでもJS簡易エディタが動くように

category-icon

 こんばんは。ppBlogでの記事作成は、テキストエリアに、タグ入力支援ツールバーの助けをかりて、(ベタに)ぐいぐい書いていくやつです。以前、IEとFirefoxがサポートしていたWYSIWYGG(contentEditable/designMode)モードを採り入れようと思いましたが、IEの生成するHTMLソースがひどくて諦めたことを、どこかに書いたと思います。今は、Safariもそれをサポートしているようですが、まぁ現状の、テキストエリア方式でいいんじゃないかなと思っています。その場でプレビュー機能も付いてますし。

 で、このタグ入力支援の機能が、グーグルのChromeでうまく動かないなと前々から認識はしていたんですが、今回調べてみました。IEとの切り分けに、

if(document.getSelection){ // Firefox などIE以外のブラウザ

としていたのが駄目だったようです。Chromeはこれを無視していたようで。なので、この部分はwindow.getSelection()を使えばOKでした。でも、今や、IE以外のブラウザはみんな同じ仕様に沿ってるようですし(2-3年前はOperaが遅れていた)、IEとそれ以外という大まかな切り分けで行けば良さそうです。なので、よくある、テキストエリアのマウスカーソルのある位置に文字列を挿入するというスクリプトは以下のような感じで行けるかなと思います。ここでは汎用性のあるサンプルを載せます。ppBlog用のlib.jsには、これを若干modifyして載せます。

/* テキストエリアでカーソルで指定したポイントあるいは文字列に新たな文字列を挿入するスクリプト */

var Caret = {
 getArea : function(){ return document.getElementById("ta");}, // ID名ta(適当)というテキストエリアを用意
 selection : '', // ここに選択したテキストを収納する
 get : function(){ // テキスト選択メソッド
  var area = Caret.getArea();
  /*@cc_on@*/
  /*@if(1)
  if(!document.selection.createRange()) area.focus();
  Caret.range =  document.selection.createRange().duplicate();
  return Caret.selection = Caret.range.text;
  @else@*/
  return Caret.selection = area.value.substring(area.selectionStart, area.selectionEnd);
  /*@end@*/
 },
 set : function(string){ // 選択テキストを指定文字列に置換する
  var area = Caret.getArea();
  /*@if(1)
   if(Caret.selection.length > 0){
    Caret.range.text = string;
    Caret.range.select();
   } else {
    area.focus();
    Caret.range = document.selection.createRange().duplicate();
    Caret.range.text = string;
   }
  @else@*/
  if(Caret.selection.length >= 0 && area.selectionStart >= 0){
    var s = area.selectionStart, scrollTop = area.scrollTop;
    area.value = area.value.slice(0, s) + area.value.slice(s).replace(Caret.selection, string);
    area.setSelectionRange(eval(s + string.length), eval(s + string.length));
    area.scrollTop = scrollTop; // Firefoxでカーソルがトップに戻らないための処理
    area.focus();
  } else area.value += string;
  /*@end@*/
 }
}

 IEだけが違うと分かっているので、条件コンパイルを使って、動作の切り分けをしています。Firefox系で、指定位置に文字列を挿入した後、フォーカスが、テキストエリアのトップに戻ってしまうのを防ぐ処理を入れています。これはFirefox1.5で見られて、今はどうかなぁと思ったら相変わらずでした(--)

 →参考リンク「 Firefoxでテキストエリア内のマウスカーソルが最初に戻ってしまう件Link

 一応、簡単なデモページを挙げておきます。

  →上のスクリプトを使った、簡易JavaScriptエディタ。http://p2b.jp/demo/jseditor.htmlLink

 このデモにあるHTMLビューは悪くないなぁ。

— posted by martin at 10:00 am   commentComment [0]  pingTrackBack [0]

T: Y: ALL: Online:
Created in 0.0049 sec.
prev
2009.3
next
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31