ずっと工事中

〜ご不便をおかけいたします〜

IE6のmax-widthとmin-widthと、expressionについての覚え書き [CSS, JavaScript]

max-widthのよくあるサンプル

max-widthを使ったちょっとしたサンプルを作る機会があって、IE6でに対応しようと思って検索してみたらIE独自拡張のexpressionを使っているものが多かったです。概要はこんな感じでしょうか。

*html #container {
	width : expression( document.body.clientWidth > 1000 ? '1000px' : 'auto' );
}

ただしこの記述については少し気になる所があって、検索に引っかかる割りには指摘をしている記事が見当たらなかったので、その点について書いておきます。

CSSで該当した要素はthisで参照できる

上の例でCSSで要素を指定しておきながら、documentからたどって該当要素を探しているのは少し効率が悪いですね。更に#containerとdocument.bodyでは要素が違うので、場合によっては予期しない動作をするかもしれません。
通常はexpression内で以下のようにthisを使って、selectorの該当要素を参照します。

*html #container {
	width : expression( this.clientWidth > 1000 ? '1000px' : 'auto' );
}

expressionはbehaviorとセットで使って1度だけ実行する

更にexpressionは画面サイズを変更したり、マウスを動かしただけでも実行されるため、非常に重くなることがあります。上記程度のソースではそれほど問題ないのかもしれませんが(それも要素が多くなれば分からない)、できるだけ軽くする工夫は必要ですね。これについてはbehaviorを使い、expression内でbehaviorを消すことで、expressionの動作を1回限りに抑えることができます(注、widthの場合は消したら表示が戻ってしまうからbehaviorを使うとも言える)。

*html #container {
	behavior: expression( (function( el ) {
		el.style.width = el.clientWidth > 1000 ? '1000px' : 'auto';
		el.runtimeStyle.behavior = 'none';
	})( this ));
}

behaviorを使うとwidthの指定ができないので、要素のスタイル属性のwidthに直接書き込んでいます。

expressionでは2行に渡ってJavaScriptを書くことができない(セミコロン;を入れるとエラーになる)のでfunctionでラップしています。更にfunction内ではthisで要素を参照できない(←こっちはJSの仕様、スコープが変わるとthisも変わる)のでthisを引数で渡しています。

runtimeStyleはstyleよりも優先度が高いので採用していますが、!importantよりは弱いようなので、普通は「el.style.behavior = 'none';」でもいいような気はします。ダメなケースはあると思うので、その辺は臨機応変に。

min-width

max-widthの応用です。

*html #container {
	behavior: expression( (function( el ) {
		el.style.width = el.clientWidth < 500 ? '500px' : 'auto';
		el.runtimeStyle.behavior = 'none';
	})( this ));
}

max-width + min-width

*html #container {
	behavior: expression( (function( el ) {
		el.style.width = 
			el.clientWidth < 500 ?	'500px':
			el.clientWidth > 1000 ? '1000px':
						'auto';
		el.runtimeStyle.behavior = 'none';
	})( this ));
}
counter

オブジェクトで共有するプライベート変数や関数を作る方法 [JavaScript, クロージャ]

友人に聞かれた内容のメモ。JavaScriptでは一般的な使い方なのですが、privateやpublicで指定できないので、初めのうちは結構戸惑いますね。

共有変数・関数をオブジェクトのプロパティ・メソッドで作る

まず、共有プロパティを使ったメソッドが存在するオブジェクトを普通に作ってみます。

var obj = {
  // 内部で使うための共有プロパティ
  prop0: 0,
  
  // 内部で使うための共有メソッド
  method0: function() {
    return ++this.prop0;
  },
  
  // 外部から使うためのメソッド
  method1: function() {
    alert(this.method0());
  }
}

obj.method1();	// 1と表示
obj.method1();	// 2と表示

ただし、prop0やmethod0は内部だけで使いたいのに、外部からもアクセスできてしまいます。
↓上からの続き

obj.prop0++;	// 外部から値を変えられたり
obj.method0();	// 外部から実行されたり

純粋に内部で使うための変数や関数を外部に見せてしまうのは良くないですね。JSの場合は特にメソッドのオーバーライドができてしまうから何かとトラブルの元。

obj.method0 = 'foo';	// 外部からオーバーライドされたりする
obj.method1();		// this.method0 is not a function

オブジェクトのプロパティやメソッドは外部での使用に限るに越したことはありません。

プライベート変数・関数

functionで一段スコープを下げるだけで簡単にプライベート変数を実現できます。
オブジェクトのプライベート変数はエンクロージャの内部変数(つまりオブジェクトの外)で作っておき、オブジェクト自身はエンクロージャからreturnで戻します。

// エンクロージャの戻り値(作成するオブジェクト)をobjに代入
var obj = function() {
  
  // オブジェクトで使うプライベート変数
  var prop0 = 0;

  // オブジェクトで使うプライベート関数
  var func0 = function() {
    return ++prop0;
  };

  // オブジェクト本体(objに返す)
  return {
    
    // 外部から使うメソッド1
    method1: function() {
      alert(func0());
    },

    // 外部から使うメソッド2
    method2: function() {
      func0();
      alert(func0());
    }
  };
}();	// ←カッコ()付きなので無名関数(エンクロージャ)が実行される

obj.method1();	// 1と表示
obj.method2();	// 3と表示
// ↑func0を通してprop0の値が保持されている証拠

method1はクロージャとして働きます。その中で使われるfunc0は無名関数実行後も保持されます(レキシカル関数)。

更にfunc0もクロージャなので、その中で使われるprop0も保持されます(レキシカル変数)。こういう工夫ってJSならではですね。

クロージャについてはまたの機会にでも。

counter

DOMのノードリスト(HTMLCollectionやNodeList)の配列化 [JavaScript]

珍しく更新頻度が高いですが、前回の記事をチェックしているときに気付いたので、忘れないうちにメモ。

今回の記事は、一般的にこういう使い方も知られています、という内容。

Arrayクラスのメソッドをノードリストのメソッドとして実行

getElementsByTagNameで取ってきたDOMエレメント(HTMLLIElement)の配列風オブジェクトHTMLCollectionや同じくquerySelectorAllのNodeListは、経験上なんとなくArrayクラスのメソッドが使えない気がしてたのですが、(↓並びを逆にしようとする一例)

// li要素列を取得(HTMLCollection)
var els = document.getElementsByTagName('li');

// Arrayクラスのreverseメソッドをelsのメソッドとして実行
Array.prototype.reverse.call(els);  // エラー

非破壊メソッドなら動く

これ↓はHTMLCollectionやNodeListでも動きました。

// 配列化
els = Array.prototype.slice.call(els);

// reverseメソッドを実行
els.reverse();

sliceは引数を渡さないと返り値はそのままという事を利用した配列化です。よくargumentsの配列化で使われる式ですね。

ノードリストの配列化を最短で書くと、

ちなみにArray自身にもArrayクラスのメソッドがあるみたいなので、

els = Array.slice.call(els);

という記述もできるみたい。

分ったこと

HTMLCollectionやNodeListは、Arrayクラスの破壊メソッド(オブジェクト自身を書き換える)はダメでも、非破壊メソッドなら動く。

ちなみに

  • なぜ配列化したいのかというと、Arrayクラスには便利なメソッドが沢山あるから。しかもDOMでアクセスするよりも速いらしいです。
  • HTMLCollectionは動的オブジェクト(例えば取得後でも該当要素を動的に追加すると、HTMLCollectionの要素も増える)、NodeListは静的オブジェクト(取得した瞬間から変化しない)です。
  • 一般的には配列化するときはループでコピーします。
// for
var ar = [];
for (var i = 0; i < els.length; i++) {
  ar[i] = els[i];
}

// while
var i = els.length;
var ar = new Array(i);  // []でもいけますが一応。
while (i--) {
  ar[i] = els[i];
}
counter

classNameから1つのクラスを抽出する正規表現 [JavaScript]

多分誰かが既に作っているとは思いますが、練習も兼ねてelement.classNameから1つのクラスを抽出する正規表現を作ってみました。

new RegExp('(^| +)' + className + '($| +)')

jQueryのソースを見ていたら、

  • スペース' 'で区切って配列化
  • 配列からclassNameのを探して、見つかったら要素を削除
  • 配列をjoin

という操作をしていたから、もう少しいい方法はないかと思って考えてみました。

用途

クラスを追加

var addClassName = function(el, className) {
  var reg = new RegExp('(^| +)' + className + '($| +)');
  if (!reg.test(el.className))
    el.className += ' ' + className;
};

クラスを削除

var removeClassName = function(el, className) {
  var reg = new RegExp('(^| +)' + className + '($| +)');
  el.className = el.className.replace(reg, ' ');
};

クラスをトグル

var toggleClassName = function(el,className) {
  var reg = new RegExp('(^| +)' + className + '($| +)');
  var res = el.className.replace(reg, ' ');
  el.className  = ((res == el.className) ? (el.className + ' ' + className) : res);
};

クラスで要素を取得

var getElementsByClassName = function(className, tagName) {
  var par = this == window ? document : this,
  reg = new RegExp('(^| +)' + className + '($| +)'),
  nodeList = [];
  if (tagName === undefined)  tagName = '*';
  var el = par.getElementsByTagName(tagName);
  for (var i = 0; i < el.length; i++) {
    if (reg.test(el[i].className))
      nodeList.push(el[i]);
  }
  return nodeList;
};

追加・削除では余分なスペースが残りますが、実用上は問題ないのでそのままにしています。消したければ.replaceを追加すればいいです。

追記

コメントより、querySelectorAllがあるブラウザで分岐するタイプです。

var getElementsByClassName = function(className, tagName) {
  var par = this == window ? document : this;
  if (tagName === undefined) var tagName = '';
  if (par.querySelectorAll)
    return par.querySelectorAll(tagName + '.' + className);
  reg = new RegExp('(^| +)' + className + '($| +)');
  var nodeList = [];
  if (tagName === '')  tagName = '*';
  var el = par.getElementsByTagName(tagName);
  for (var i = 0; i < el.length; i++)
    if (reg.test(el[i].className))
      nodeList.push(el[i]);
  return nodeList;
};

querySelectorAllのときは戻り値は配列ではないので、Arrayクラスのメソッドは使えないです。Array.prototype.reverse.call(nodeList)みたいにしてもダメでした。

確認していたら気付いたのですが、IE8のquerySelectorAll(とquerySelector)はβではあったような気がしたのですが、なくなっちゃったのでしょうか。

counter

IE以外でもdocument.createStyleSheetを使えるようにする [JavaScript, CSS]

Firefoxでもdocument.createStyleSheet的なものを使いたかったので、ちょっと作ってみました。

if (document.createStyleSheet === undefined) {
  document.createStyleSheet = function(url) {
    if (url) {
      var el = document.createElement('link');
      el.rel = 'stylesheet';
      el.href = url;
    } else {
      var el = document.createElement('style');
    }
    el.type = 'text/css';
    document.getElementsByTagName('head')[0].appendChild(el);
    return el.sheet;
  };
}

単にstyleタグをheadに作っているだけだったりしますが

使い方

var userCSS = document.createStyleSheet();

内部スタイルを作るときはURLを指定しなくてもいいです。

戻り値は該当するdocument.styleSheets[n]です。これはIEの実装に合わせました。

何番目かを指定できるオプションの第二引数は無効です。というか、IE6でも正常に動かないっぽいのでやめました(←詳細まで調べていないですが)。

↓みたいな使い方にも対応していません。

document.createStyleSheet(document.body.style.backgroundColor='blue');

既存のスタイルシートには手を入れないで、自由に弄れる内部スタイルシートが欲しかったので作ってみました。その辺はまたの機会にでも。

その他

簡単に作ったものなので、何かあれば修正します。

2009/10/24 早速コメント1と3を受けて修正しました。

counter

newをつけてもつけなくても動くコンストラクタ [JavaScript]

Arrayみたいにnewをつけてもつけなくても、新しく作ったインスタンスを返す関数が面白いなーと思って、多分こんな感じなんだろうと想像してみました。

var Foo = function() {
  if (this === window)  return new arguments.callee();
  this.prop1 = 'prop1';
};
Foo.prototype.prop2 = 'prop2';

インスタンスの生成は

var foo = new Foo();

でも

var foo = Foo();

でもいいです。

コンストラクタと普通の関数を兼用するときにも応用できそう。

もしくは「JavaScript The Good Parts」に書いてあったnewの欠点を解消するとか。

イベントハンドラに充てるとよくなさそうですが、そういう使い方をされそうも無いので、対応策は割愛です。

追記

コンストラクタかどうかの判定はこちらの方がよさそうです。

var Foo = function() {
  var Class = arguments.callee;
  if (this instanceof Class) return new Class();
  this.prop1 = 'prop1';
};
Foo.prototype.prop2 = 'prop2';

counter
<<  November,2009  
S M T W T F S
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          
ギャラリー
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
  • Eclipse + ChromeでJavaScriptデバッグ [Google Chrome Developer Toolsインストール編]
記事検索
Recent Comments
訪問者数

    • livedoor Readerに登録
    • RSS
    • livedoor Blog(ブログ)