px単位の値を取得する(その2)

JavaScript の勉強を始めた頃(去年の今頃)こういう日記を書いてました。
IEで width: "3em", width: "auto" から px単位の値を取得する - latest log
uuStyle.toPixel() を、よりクロスブラウザなコードにしてみました。

<script>
var _ie = document.uniqueID;
var _webkit = navigator.userAgent.indexOf("WebKit") > 0;
var _int = parseInt;
var _runstyle = _ie ? "currentStyle"
                    : document.defaultView.getComputedStyle;
var IMPORTANT = "important";
var POSITION = "position";
var ABSOLUTE = "absolute";
var DISPLAY = "display";
var BLOCK = "block";
var LEFT = "left";

var uuStyle = {
  // uuStyle.toPixel - covert unit
  //    toPixel(node, 123)    -> 123
  //    toPixel(node, "12px") -> 12
  //    toPixel(node, "12pt") -> 16
  //    toPixel(node, "12em") -> 192
  toPixel: function(elm,     // @param Node: context
                    value) { // @param String/Number:
                             // @return Number: pixel value
    if (typeof value === "string") {
      var st = elm.style, mem1 = st[LEFT], rs, mem2, mem3;

      if (_ie) {
        rs = elm.runtimeStyle, mem2 = rs[LEFT]; // keep !important value
        // overwrite
        rs[LEFT] = elm[_runstyle][LEFT];
        st[LEFT] = value;
        // get pixel
        value = st.pixelLeft;
        // restore
        st[LEFT] = mem1;
        rs[LEFT] = mem2;
      } else {
        // overwrite
        if (_webkit) {
          mem2 = st.getPropertyValue(POSITION);
          mem3 = st.getPropertyValue(DISPLAY);
          st.setProperty(POSITION, ABSOLUTE, IMPORTANT);
          st.setProperty(DISPLAY,  BLOCK,    IMPORTANT);
        }
        st.setProperty(LEFT, value, IMPORTANT);
        // get pixel
        value = _int(_runstyle(elm, "")[LEFT]);
        // restore
        st.removeProperty(LEFT);
        st.setProperty(LEFT, mem1, "");
        if (_webkit) {
          st.removeProperty(POSITION);
          st.removeProperty(DISPLAY);
          st.setProperty(POSITION, mem2, "");
          st.setProperty(DISPLAY,  mem3, "");
        }
      }
    }
    return value || 0;
  },

  // uuStyle.getPixel - get pixel value
  //    getPixel(node, "left")
  //    getPixel(node, "width")
  getPixel: function(elm,    // @param Node:
                     prop) { // @param String: style property name
                             // @return Number: pixel value
    function dim(horizontal) {
      var r = elm.getBoundingClientRect();
      return horizontal ? (r.right - r.left) : (r.bottom - r.top);
    }
    var rv;

    if (_ie) {
      switch (prop) {
      case "width":  return elm.clientWidth  || dim(1);
      case "height": return elm.clientHeight || dim(0);
      }
      rv = elm[_runstyle][prop];
      (rv === "auto") && (rv = uuStyle.toPixel(elm, rv));
    } else {
      rv = _runstyle(elm, "")[prop];
    }
    return _int(rv) || 0;
  }
};
</script>

説明

uuStyle.toPixel(elm, value) は、値(value)が ある要素(elm)で 何px なのかを返します。端数は切り捨てます。

// 20pt が body要素で 何px に相当するのか調べる
alert(uuStyle.toPixel(document.body, "20pt")); // windows なら 27 または 26

uuStyle.getPixel(elm, prop) は、スタイル(prop)の計算済みの値が 何px なのかを返します。prop には、"left", "top", "width", "fontSize" など、長さに関係するプロパティ名を指定します。width: "auto" が指定されている場合でも適切な値を取得できます。

alert(uuStyle.getPixel(document.body, "fontSize")); //  windows なら 16

処理を読み解くヒントをいくつか

  • st.setProperty(prop, value, "important") は !important を js レベルで設定する方法の一つです。
    • !important を確実に取り去るには、removeProperty(prop) します。
      • st.setProperty(prop, value, null) だとうまくいきません。
  • WebKit(Safari, Google Chrome) は、ブロックレベル + 絶対配置要素以外では left の値がゼロになるため、強制的に display: block + position: absolute の状態にして、left 取得後に戻しています。
  • uuStyle.getPixel(インライン要素, "width") は、WebKit, Gecko でゼロになりますが、IEOpera では予想外の値が返ります。これは "width" 以外にも "height", "top", "right", "bottom", "right" などでも同じです。

テストコード

<!doctype html><html><head><title></title>
<script>
var _ie = document.uniqueID;
var _webkit = navigator.userAgent.indexOf("WebKit") > 0;
var _int = parseInt;
var _runstyle = _ie ? "currentStyle"
                    : document.defaultView.getComputedStyle;
var IMPORTANT = "important";
var POSITION = "position";
var ABSOLUTE = "absolute";
var DISPLAY = "display";
var BLOCK = "block";
var LEFT = "left";

var uuStyle = {
  // uuStyle.toPixel - covert unit
  //    toPixel(node, 123)    -> 123
  //    toPixel(node, "12px") -> 12
  //    toPixel(node, "12pt") -> 15.996
  //    toPixel(node, "12em") -> 12em * 1
  toPixel: function(elm,     // @param Node: context
                    value) { // @param String/Number:
                             // @return Number: pixel value
    if (typeof value === "string") {
      var st = elm.style, mem1 = st[LEFT], rs, mem2, mem3;

      if (_ie) {
        rs = elm.runtimeStyle, mem2 = rs[LEFT]; // keep !important value
        // overwrite
        rs[LEFT] = elm[_runstyle][LEFT];
        st[LEFT] = value;
        // get pixel
        value = st.pixelLeft;
        // restore
        st[LEFT] = mem1;
        rs[LEFT] = mem2;
      } else {
        // overwrite
        if (_webkit) {
          mem2 = st.getPropertyValue(POSITION);
          mem3 = st.getPropertyValue(DISPLAY);
          st.setProperty(POSITION, ABSOLUTE, IMPORTANT);
          st.setProperty(DISPLAY,  BLOCK,    IMPORTANT);
        }
        st.setProperty(LEFT, value, IMPORTANT);
        // get pixel
        value = _int(_runstyle(elm, "")[LEFT]);
        // restore
        st.removeProperty(LEFT);
        st.setProperty(LEFT, mem1, "");
        if (_webkit) {
          st.removeProperty(POSITION);
          st.removeProperty(DISPLAY);
          st.setProperty(POSITION, mem2, "");
          st.setProperty(DISPLAY,  mem3, "");
        }
      }
    }
    return value || 0;
  },

  // uuStyle.getPixel - get pixel value
  //    getPixel(node, "left")
  //    getPixel(node, "width")
  getPixel: function(elm,    // @param Node:
                     prop) { // @param String: style property name
                             // @return Number: pixel value
    function dim(horizontal) {
      var r = elm.getBoundingClientRect();
      return horizontal ? (r.right - r.left) : (r.bottom - r.top);
    }
    var rv;

    if (_ie) {
      switch (prop) {
      case "width":  return elm.clientWidth  || dim(1);
      case "height": return elm.clientHeight || dim(0);
      }
      rv = elm[_runstyle][prop];
      (rv === "auto") && (rv = uuStyle.toPixel(elm, rv));
    } else {
      rv = _runstyle(elm, "")[prop];
    }
    return _int(rv) || 0;
  }
};
</script>
<style>
p { margin: 0; padding: 0 }
div { position: absolute; background-color: #eef; opacity: 0.8 }
.tgt0 { border: 3px dotted skyblue }
.tgt1 { border: 3px solid red }
.tgt2 { border: 3px solid green }
.tgt3 { border: 3px solid blue }
.tgt4 { border: 3px solid pink }
.tgt5 { border: 3px solid tomato }
.tgt6 { border: 3px solid yellowgreen }
.tgt7 { border: 3px solid darkcyan }
#out { z-index: 4; top: 50px; }
</style>
<script>
function calcStyle() {
  var body = document.body;
  var tgt0 = document.getElementById("tgt0");
  var tgt1 = document.getElementById("tgt1");
  var tgt2 = document.getElementById("tgt2");
  var tgt3 = document.getElementById("tgt3");
  var tgt4 = document.getElementById("tgt4");
  var tgt5 = document.getElementById("tgt5");
  var tgt6 = document.getElementById("tgt6");
  var tgt7 = document.getElementById("tgt7");
  var rv = [];
  var fn1 = uuStyle.toPixel;
  var fn2 = uuStyle.getPixel;

  rv.push("<b>toPixel</b>");
  rv.push('<p class="tgt0">', ["12px", fn1(tgt0, "12px")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt0">', ["12pt", fn1(tgt0, "12pt")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt0">', ["12em", fn1(tgt0, "12em")].join(" -&gt; "), "</p>");

  rv.push("<b>getPixel</b>");
  rv.push('<p class="tgt1">', ["left:20px", fn2(tgt1, "left")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt2">', ["left:20em", fn2(tgt2, "left")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt3">', ["left:20pt", fn2(tgt3, "left")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt4">', ["left:auto", fn2(tgt4, "left")].join(" -&gt; "), "</p>");

  rv.push('<p class="tgt1">', ["width:auto", fn2(tgt1, "width")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt2">', ["width:auto", fn2(tgt2, "width")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt3">', ["width:auto", fn2(tgt3, "width")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt4">', ["width:auto", fn2(tgt4, "width")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt5">', ["width:50%",  fn2(tgt5, "width")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt5">', ["height:50%", fn2(tgt5, "height")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt6">', ["width:20em",  fn2(tgt6, "width")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt6">', ["height:20em", fn2(tgt6, "height")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt7">', ["width:auto",  fn2(tgt7, "width")].join(" -&gt; "), "</p>");
  rv.push('<p class="tgt7">', ["height:auto", fn2(tgt7, "height")].join(" -&gt; "), "</p>");

  rv.push(["20pt", fn1(document.body, "20pt")].join(" -&gt; "));

alert(uuStyle.getPixel(document.body, "fontSize"));


  document.getElementById("out").innerHTML = rv.join("<br />");
}
</script>
</head>
<body>
<div>
  <input type="button" value="calc" onclick="calcStyle()" />
</div>

<div class="tgt0" id="tgt0" style="left: 200px">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</div>
<div class="tgt1" id="tgt1" style="top: 200px; left: 20px">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</div>
<div class="tgt2" id="tgt2" style="top: 250px; left: 20em">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</div>
<div class="tgt3" id="tgt3" style="top: 300px; left: 20pt">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</div>
<div class="tgt4" id="tgt4" style="top: 350px;">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</div>
<div class="tgt5" id="tgt5" style="top: 400px; width: 50%; height: 50%">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</div>
<div class="tgt6" id="tgt6" style="top: 450px; width: 20em; height: 20em">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</div>
<p class="tgt7" id="tgt7">
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
  tgttgttgttgttgttgttgttgttgttgttgttgttgttgt
</p>

<div id="out"></div>
</body></html>