belatedPNGと幸せにくらす為の小さなコツと小技
この記事は賞味期限切れです。(更新から1年が経過しています)
様々な理由から多くの方がAlphaImageLoaderよりもbelatedPNGを活用されているようですが、 単純明快な前者と違って、後者は便利な反面ブラックボックスな部分も多いです。 今回のお話は、そんなbelatedPNGを使用する上で注意すべき点と、困ったときのおまじない。
- 前提
- ここで言う「belatedPNG」は本家ではなく、
本家をフォークしている「jquery-belatedpng.js」を指しています
何をしてくれているのか、知っておく
まず、belatedPNG が行ってくれる処理をおさらいしてみましょう。 簡単に書いていますが、結構面倒な事を一手に引き受けてくれている事がわかります。
- fixPng() をあてた要素の複製をVMLで生成してそれを表示する
- 元要素のスタイルの変更を検知してVML要素にも変更を加える
- VML要素へのマウスイベントを元要素に伝えてくれる
別にbelatedPNGに限った話ではないですが、どのような処理をしているのか知っておく事で何か不具合が発生した時に対処しやすくなりますね。 実際にどのようなVMLが生成されてどう配置されているのかは、IEの開発者ツール等で見る事ができますので困ったときは覗いてみましょう。
fixPng()のセレクタは適切な物を
たまに拝見するのが、次のようにゆるいセレクタで fixPng() を行っているコードです。
$("div, img").fixPng();
これは大変極端な例ですが、全てのdiv要素とimg要素に対してbelatedPNGの処理を行っています。 多くの場合このコードは正常に動作しますが、正直好ましくないと思うのです。
プレーンでない物が増えていく
余計なものまで処理させてしまうコストの面もそうなのですが、 それ以上に重要なのは、予想外の物までVML化されてしまう点です。 そこで困るのはフロント開発のエンジニアさん達です。 プレーンな要素を想定して組み上げたアプリが何故か動かずに頭を抱えたり、 問題解決に辿り着くまでに大きな回り道をさせられてしまうかもしれません。
belatedPNG の内部で処理不要な要素(PNG画像が関わっていない物)はスルーしてくれているので、 そこまで大きな問題は発生しないかもしれませんが、「よくわからないもの」は出来るだけ少なくしておきたいですね。
適切なセレクタで実行しよう
そんなわけで、渡すセレクタは適切に絞った物を設定する事をお勧めしたいです。 可能なら、処理するクラス名を定義してしまうのが安全かもしれません。
$(".fixpng").fixPng();
処理する環境を判別する
belatedPNGが必要な環境はIE9未満(透明度を使用する場合)なので、その環境下だけで処理を行うようにします。 例えば条件分岐コメントを利用する場合はこの様な感じで。
<!--[if lt IE 9]>
<script src="jquery.belatedpng.js"></script>
<script>
$(".fixpng").fixPng();
</script>
<![endif]-->
ミニファイなどの都合でJS内で判別したい場合は、「CSSでopacityをサポートしていない」「IEである」という条件で如何でしょう。
if(! $.support.opacity && !! ActiveXObject){
$(".fixpng").fixPng();
}
「VMLが使用可能か」という条件でも良いのですが、下記リンクで紹介されている様に、少し長くなってしまいます。
cf) javascript – How do you detect support for VML or SVG in a browser – Stack Overflow
コンテナ以下の要素の透明度をまるごと変更する
belatedPNG(VML)の特徴の一つとして、親要素のopacityを継承しない問題がありますが、 あるコンテナ以下の要素の透明度をまるごと変えたい時はどうしたらよいでしょうか。
<div class="holder">
<img src="foo.png" alt="" />
<img src="bar.png" alt="" />
<img src="baz.png" alt="" />
</div>
<script>
$(".holder img").fixPng();
$(".holder").fadeOut(); // 画像の透明度が変わらない!
</script>
上のコードで “.holder” まるごとフェードアウトしたい場合、例えば次のようにしてみます。
$(".holder, .holder [vmlInitiated]").fadeOut();
belatedPng はVMLに書き出す際に元要素に「vmlInitiated」という属性をつけるので、それを利用してみた例です。 あるいは、前述のようにクラスが決まっているのであればそれを指定してしまった方が効率的ですね。
関数化してみる
毎度面倒な場合は関数化してみましょう。
// $.fn.fadeOut() のラッパー
$.fn.belatedFadeOut = function(){
this.fadeOut.apply(this.find("[vmlInitiated]"), arguments);
return this.fadeOut.apply(this, arguments);
};
// $.fn.fadeIn() のラッパー
$.fn.belatedFadeIn = function(){
this.fadeIn.apply(this.find("[vmlInitiated]"), arguments);
return this.fadeIn.apply(this, arguments);
};
// $.fn.animate() のラッパー
$.fn.belatedAnimate = function(){
if(arguments[0].opacity !== undefined){
args = [].slice.call(arguments);
args[0] = {opacity: arguments[0].opacity};
this.animate.apply(this.find("[vmlInitiated]"), args);
}
return this.animate.apply(this, arguments);
};
// fadeOut等のかわりに使います
$(".holder").belatedFadeOut(1000);
クラスによるbackground-imageの変更を適用させる
例えば次のように、クラスによって要素の背景画像が変わるCSSを組んでいるとしましょう。
<style>
.box { background-image: url(red.png); }
.box.blue { background-image: url(blue.png); }
</style>
<div class="box"></div>
<script>
if(! $.support.opacity && !! ActiveXObject){
$(".box").fixPng();
}
$(".box").addClass("blue"); // 通常は背景が blue.png に変わるが…
</script>
通常、”.box” に “blue” クラスをあてれば背景が “blue.png” に切り替わるはずなのですが、 belatedPNGが効いた要素では残念な事に切り替わりません。
なぜなのか
belatedPNGでは、VML要素と元要素のスタイルの同期をとるために “propertychange” というイベントを利用しています。 これは、要素の属性に変更が加えられた際に発火するイベントです。
元要素のスタイルに変更が加えられた時にこのイベントが発火して、 VMLの表示を元要素のスタイルにあわせて更新するという仕組みです。 ですが、背景の更新は背景に関する変更がなされた場合にしか行われません。 つまり、クラス名の変更を行っても何も起きないのです。
独自属性で半ば強引に解決する
要するに、元要素の「背景に関する属性」を変更すれば更新されるわけです。 コードを読むと「属性名に”background”という文字を含んでいるかどうか」で判別しているので、 こんな感じで更新されるようになります。
$(".box").prop("update-background", true);
なお、属性の値は「変更」される必要はなく、値を設定するだけで発火しますので、trueを渡し続けても大丈夫です。
まとめ
belatedPNG は、本来出来ない事を出来るようにしてしまう魔法のようなスクリプトですが、 魔法には動く為の仕組みがあり、動かす為のルールもあります。 正しい用法を守って、計画的に利用していきたいですね。
テーマの都合上あまり良くない点ばかり書き連ねる事になってしまいましたが、 個人的に似たライブラリを再発明してみようとした事があり、その面倒さに閉口した記憶があります。 完成に到るまで本家ならびにjQueryプラグインを書き上げてくれた方にこの場をかりてお礼を申し上げます。
いつの日か、ライブラリの命名通り、透過PNGの対応自体が「時代遅れ」になる事を待ち望んで。
コメント