IE6で透過PNGをサポートする

まず、IE6では24bit png(α成分による透過)をサポートしていません。256色pngの透過はサポートしていますが、256色pngではセクシーな表現ができんのですよ。

IE8β1が出たこの時期にいまさらIE6用に透過png(24bit png)機能をサポートするのはどうなんだろう? と思いましたが、あと2年ぐらいは使われそうな気がするので、uupaa.jsの機能の一部としてサポートすることにしました。

参考にしたのは、http://blog.kaburk.com/lang/html/ie-penetration-png.html とか。



使い方。

  1. 24bitのpng画像を張っとく。
  2. 縦横1ピクセルの透明なgif(名前は1x1.gif)を用意し適当なディレクトリに置いとく(後で遅延ロードして使います)。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>24bit PNG test</title>
<!--[if IE]><script type="text/javascript" src="../lib/xpath.js"></script><![endif]-->
<script type="text/javascript" src="../uupaa.js"></script>
<style>
body { background-color: black; }
div { background: black url(../img/burning_sunset.jpg); }
</style>
</head>
<body>

<div>
<img src="../img/png24.png" alt="" />  <!-- 24bit PNG image -->
<img src="../img/kuma24.png" alt="" />  <!-- 24bit PNG image -->
</div>
<img src="../img/png24.png" alt="" />  <!-- 24bit PNG image -->
<img src="../img/kuma24.png" alt="" />  <!-- 24bit PNG image -->

</body>
</html>


JavaScriptで動的にpng画像を追加する場合は、uu.fixIEPNG24(動的に生成したimg要素) を呼び出します。
uu.fixIEPNG24()に渡す引数は、要素の配列でもOKです。

んで、以下が今回の機能追加に関係する部分です。1x1.gif は、uupaa.jsと同じディレクトリにあるものとします。

var uu = window.uu = {
  "#": {
     basePath: "",           // uupaa.js base path
     enablePNG24: true,      // true: IE5〜6でAlphaPNGを自動的にサポートする, false: しない
   },
  /** uupaaへの通知
   *
   * スクリプト読み込み完了通知: notify("included", script-file-name)
   */
  notify: function(msg, param) {
    switch (msg) {
    case "IEHack":
      uu.fixIEPNG24(uu.xsnap('//img[translate(substring(@src,string-length(@src)-3),".PNG",".png")=".png"]'));
      break;
    }
  },
  /** support alpha PNG(PNG-24) for IE5.5/IE6.0
   *
   * @param element/array elms   image-element or [image-element, ...]
   */
  fixIEPNG24: function(elms) {
    if (!(/MSIE (5\.5|6\.0)/.test(navigator.userAgent) && navigator.platform == "Win32")) { return; }
    if (!uu.id("1x1")) {
      // <img id="1x1" src="${uu["#"].basePath}/1x1.gif" ... />
      var e = document.createElement("img");
      e.id = "1x1";
      e.style.display = "none";
      document.body.appendChild(e);
      e.onload = function() { uu.fixIEPNG24(elms); };
      e.src = uu["#"].basePath + "1x1.gif";
      return;
    }
    var src1x1 = uu.id("1x1").src;
    elms = uu.isA(elms) ? elms : [elms];
    elms.forEach(function(v) {
      var org = { w: v.width, h: v.height };
      v.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + v.src + '",sizingMethod=scale)';
      uu.mix(v, { src: src1x1, width: org.w, height: org.h });
    });
  }
};

// IE Hack
if (uu.ua.ie && uu["#"].enablePNG24) {
  uu.registWindowEvent("onload", function(){ uu.notify("IEHack"); });
}

// search "uupaa.js" file path.
uu.xsnap('//script[substring(@src,string-length(@src)-7)="uupaa.js"]', "src").forEach(function(v) {
  uu["#"].basePath = v.replace(/uupaa\.js/, "");
});


足りない部品は、こっち

var uu = window.uu = {
  /** protected member */
  "#": {
    events: {},
  },
  ua: {
    ie: !!(window.attachEvent && !window.opera), // Internet Explorerでtrue
  },
  isF: function(mix) { return typeof(mix) === "function"; }, // isFunction
  isA: function(mix) { return mix.constructor === Array; }, // isArray
  id: function(id) {
    return (typeof id === "string") ? document.getElementById(id) : id;
  },
  args: function(arg, defaultValue) {
    return (typeof arg === "undefined") ? defaultValue : arg;
  },
  mix: function(src, mixer) {
    if (typeof mixer !== "object") { return src; }
    for (var p in mixer) { src[p] = mixer[p]; }
    return src;
  },
  forEach: function(obj, iter, thisp /* = undefined */) {
    if (!obj || typeof iter !== "function") { throw TypeError(); }
    if (typeof obj.forEach === "function") { return obj.forEach(iter, thisp); }
    for (var p in obj) {
      obj.hasOwnProperty(p) && iter.call(thisp, obj[p], p, obj);
    }
    return this;
  },
  xsnap: function(xpath, attr, ctx, sort) {
    var n = document.evaluate(xpath, uu.args(ctx, document), null, uu.args(sort, true) ? 7 : 6, null);
    var rv = [], i = 0;
    attr = uu.args(attr, "");
    if (attr.length) {
      for (; i < n.snapshotLength; ++i) { rv.push(n.snapshotItem(i)[attr]); }
    } else {
      for (; i < n.snapshotLength; ++i) { rv.push(n.snapshotItem(i)); }
    }
    return rv;
  },
  registWindowEvent: function(name, fn) {
    var n = "window." + name, evt = uu["#"].events;
    if (!(n in evt)) { // at first time
      evt[n] = [];
      window[name] = function() {
        uu.forEach(evt[n], function(v) { uu.isF(v) && v(); });
      }
    }
    evt[n].push(fn);
  }
};

今回の反省会(03/09)

  • 予想外に時間かかったよ。結局一日がかりだった。
    • xpathのsubstring()の第2引数が0オリジン(0はじまり)ではなく、1オリジンな点がヤラレタ。'//img[translate(substring(@src,string-length(@src)-3),".PNG",".png")=".png"]'
    • xpathの文字列操作関数にtoUpper()やtoLower()が無く、translate()がソレであることに気付くのが遅れた。
    • 他の人が実践しているIEの独自機能であるビヘイビアも試してみたけど、
      • <style>img.alpha{ behavior:url(alpha.htc) }</style>なんてことやったら、CSSバリデーターで警告でるだろうし。
      • <img height="100" width="100" src="demo.png" class="alpha" />というコードも見かけたけど、imgにheight,withを指定する必要があったり、専用のclassを指定する必要があるのは、目的に対する手段として不適切なんじゃないかなぁと(画像サイズ変更等がしんどくなる)

追加の反省会(03/12)

  • <img id="1x1" src="1x1.gif" ... >方式は問題があることが判明
    • uupaa.jsのパスから画像の場所を検索する方式に変更
      • 余計な決め事が減ってスッキリ。これで、<img src="*.png"> ってやるだけで使える。
  • なんかコードがごっちゃりと長いので、スッキリしたコードも載せるほうが良いのかね?
    • それは他の人に任せればよさげ。探せば何ぼでも見つかるし。
      • そか。
  • 背景画像やhover等に透過png使いたいってニーズは? 現状は<img src="*.png">のみ気にしてるけど。
    • わからん。ニーズがあったら考える。

さぁ、反省おわり。

Let's スケスケ。

2008-04-12 追記:

XPathが無くても動作するように実装を変更しました。http://pigs.sourceforge.jp/wiki/index.php?uupaa.js で最新版の動作デモをやってます。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>24bit PNG test</title>
<style>
body { background-color: black; }
div { background: black url(./burning_sunset.jpg); }
</style>
</head>
<body>
<div><img src="./png24.png" alt="" /><img src="./kuma24.png" alt="" /></div>
<img src="./png24.png" alt="" /><img src="./kuma24.png" alt="" />

<script>
var uu = window.uu = {
  mix: function(mapSrc, mapMixer) {
    for (var p in mapMixer) { mapSrc[p] = mapMixer[p]; }
    return mapSrc;
  },
  toArray: function(map) {
    var rv = new Array(map.length || 0), i = 0, sz = rv.length;
    for (; i < sz; ++i) { rv[i] = map[i]; }
    return rv;
  }
};

if (document.uniqueID && /MSIE (5\.5|6\.0)/.test(navigator.userAgent) && navigator.platform == "Win32") {
  uu.fixIEPNG24 = function(elms) {
    var d = document, e = d.getElementById("1x1gif"), w, h;
    if (!e) {
      d.body.appendChild(uu.mix(d.createElement("img"), {
        id: "1x1gif", onload: function() { uu.fixIEPNG24(elms); }
      })).src = "./1x1.gif";
    } else {
      e.style.display = "none";
      ((elms instanceof Array) ? elms : [elms]).forEach(function(v) {
        w = v.width, h = v.height;
        v.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + v.src + '",sizingMethod=scale)';
        uu.mix(v, { src: e.src, width: w, height: h });
      });
    }
  }
}

if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(iter, bindThis /* = undefined */) {
    var i = 0, sz = this.length;
    for (; i < sz; ++i) { (i in this) && iter.call(bindThis, this[i], i, this); }
    return this;
  };
}

window.onload = function() {
  if (document.uniqueID) {
    var png = [];
    uu.toArray(document.images).forEach(function(v) {
      if (v.src.match(/.png$/i) && v.complete) {
        png.push(v);
      }
    });
    uu.fixIEPNG24(png);
  }
};
</script>

</body>
</html>