setExpression と removeExpression について調べ物
IE 5〜7 限定のメソッド setExpression と removeExpression について。IE8のmode8では CSS:expression は廃止された(setExpression と removeExpression も廃止されたかは不明)。
- setExpression
- getExpression
- element.setExpression(String:プロパティ名): 式または undefined を返す
- element.style.setExpression(String:CSSプロパティ名): 式または undefined を返す
- removeExpression
CSSで設定した expression を removeExpression する
<style> .alpha { width: expression(step1(this)); } </style> <script> function step1(elm) { setTimeout(function() { step2(elm); }, 0); } function step2(elm) { alert(elm.style.getExpression("width")); // "step1(this)" elm.style.removeExpression("width"); alert(elm.style.getExpression("width")); // undefined } </script>
behavior:expression との棲み分け
以下のようにすると、全ての要素について 一度だけ hoge(element) を呼び出すことができる
<style> * { behavior:expression(hoge(this)) } </style> <script> function hoge(elm) { elm.style.behavior = "none"; // この要素は、behavior:expression から呼ばれなくなる } </script>
他のライブラリ併用時に発生するコンフリクトを回避する仕組みが必要
上記の書き方でも自己完結した世界なら十分だが、同様の仕組みを持つ他のライブラリをロードすると問題が発生する(behaviorの取り合いが発生する)。
解決するには、要素の追加/削除(MutationEvent)イベントを通知する流れを作り、各ライブラリがこの流れに沿って行動を起こす必要がある。
MutationEvent は IEには実装されていないが、uupaa.js では MutationEvent をエミュレートすることでこの流れを作っている。
// === Custom Event ======================================== UU.CUSTOM_EVENT = { NOTIFY: 0x01, // without node ADD_ELEMENT: 0x02, // with node REMOVE_ELEMENT: 0x04, // with node UPDATE_ELEMENT: 0x08, RESIZE_VIEWPORT: 0x10, // resize window RESIZE_BODY: 0x20, // resize body (one shot) RESIZE_FONT: 0x40, // resize font ALL: 0xff }; uu.Class.Singleton("CustomEvent", { construct: function() { this._fn = []; // Array( [[callback function, customEvent], ... ] ) this._enable = true; this._lastEvent = 0x0; // infinitely resize guard // set event handler var me = this; uu.customEventResizeFont.attach(function() { uu.style.unit(1); me.fire(UU.CUSTOM_EVENT.RESIZE_FONT); }); uu.customEventResizeBody.attach(function() { me.fire(UU.CUSTOM_EVENT.RESIZE_BODY); }); uu.event.attach(window, "resize", this); if (UU.IE && uu.ua.version < 8) { // hook add element for IE6, IE7 if (uu.windowReady) { setAgent(); } else { window.attachEvent("onload", setAgent); } } function setAgent() { uudoc.createStyleSheet().cssText = "*{behavior:expression(uu.Class.CustomEvent._addElement(this))}"; } }, handleEvent: function(evt) { if (this._enable) { if (evt.type === "resize") { this.fire(UU.CUSTOM_EVENT.RESIZE_VIEWPORT); } } }, // uu.Class.CustomEvent.enable enable: function() { this._enable = true; }, // uu.Class.CustomEvent.disable disable: function() { this._enable = false; }, // uu.Class.CustomEvent.attach - attach event handler attach: function(fn, // Function: callback function customEvent) { // Number: customEvent, UU.CUSTOM_EVENT... this._fn.push([fn, customEvent]); }, // uu.Class.CustomEvent.detach - detach event handler detach: function(fn) { // Function: callback function var idx = -1, i = 0, iz = this._fn.length; for (; i < iz; ++i) { if (i in this._fn) { if (this._fn[i][0] === fn) { idx = i; break; } } } if (idx >= 0) { this._fn.splice(idx, 1); } }, // uu.Class.CustomEvent.fire - revalidate fire: function(customEvent, // Number(default: 0x01): customEvent node) { // Node(default: undefined): customEvent = customEvent === void 0 ? 0x01 : customEvent; if (this._enable) { if (customEvent & UU.CUSTOM_EVENT.RESIZE_VIEWPORT && customEvent & this._lastEvent) { customEvent = customEvent & ~UU.CUSTOM_EVENT.RESIZE_VIEWPORT; } this._lastEvent = customEvent; var v, i = 0; while ( (v = this._fn[i++]) ) { if (customEvent & v[1]) { v[0](customEvent, node); // callback } } } } }); // static function uu.mix(uu.Class.CustomEvent, { // uu.Class.CustomEvent._addElement _addElement: function(elm) { // @see http://d.hatena.ne.jp/uupaa/20081129/1227951320 if (elm.style.behavior === "none") { return; } // guard: text selection + drag uu.customEvent.disable(); elm.style.behavior = "none"; // disable CSS::expression uu.customEvent.enable(); if (elm.nodeType === 1) { // hook remove element (function(node) { var rm = node.removeChild; node.removeChild = function(oldChild) { var rv = rm(oldChild); uu.customEvent.fire(UU.CUSTOM_EVENT.REMOVE_ELEMENT, rv); return rv; } })(elm); uu.customEvent.fire(UU.CUSTOM_EVENT.ADD_ELEMENT, elm); } } }); uu.customEvent = null; uu.ready(function() { uu.customEvent = new uu.Class.CustomEvent(); }, 2); // 2: mid
要素が追加されたタイミング(正確にはブラウザが要素を初めて表示しようとしたタイミング)を知りたい場合は、クラスのコンストラクタ内で以下のように定義しておく。
uu.customEvent.attach() でアタッチした関数は、イベント種別(customEvent) と 追加された要素(elm) を引数にコールバックされる。
uu.Class.Singleton("IEBoostPNGBG", { construct: function() { var me = this; uu.customEvent.attach(function(customEvent, elm) { if (uu.className.has(elm, "alpha")) { // elm.className に "alpha" が含まれていればtrue me.init(elm); // 透過 png 処理開始 } }, UU.CUSTOM_EVENT.ADD_ELEMENT); } }); UU.IE && uu.ready(function() { // DOMContentLoaded でインスタンス化 uu.ieboostPNGBG = new uu.Class.IEBoostPNGBG(); });
その先にある問題
IEに欠落している機能(CSS2.1, CSS3系)を補完するには、 expression, behavior, onpropertychange を最大限に利用する必要があるが、これらはたとえ使用方法を間違えなくても、何かのはずみでブラウザをフリーズさせる能力を持っている。
再描画の度に関数が呼び出され再評価が行われるため、本当に必要な条件を編み出し、不要な再描画を避けるようにガードしてあげないと、パフォーマンスが劇的に悪化する。
これらをクリアしたとしても、resize イベントの無限ループ地獄が きっとあなたを待っている。