あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

ページズーム機能がないブラウザで、画像の拡大を含めたエラスティックレイアウトを実現するサンプルを作った

文字数で幅を指定できるエラスティックレイアウトが好きです。

CSSでレイアウトする時、各ブラウザがページズーム機能を導入した今はほとんど必要ないことかもしれませんが、一行あたりの文字数は読みやすい数というものがきっとあると思っています。

エラスティックレイアウトにしても、ページズームに対応していないブラウザでは、画像のサイズが拡大されません。そこがちょっと物足りないので、画像のズームも含めたエラスティックレイアウトを実現するためのサンプルを作ってみました。

最初に断っておくと、私はあんまりCSSを弄ることがないので、CSSのことツッコまれるとちょっとソワソワします。

HTMLサンプル

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Elasticサンプル</title>
</head>
<body>
  <h1>Elasticサンプル</h1>
  <p><img src="http://www.st-hatena.com/users/jd/jdg/profile.gif"></p>
  <div id="container">
    <div id="main">
      <h2>hoge</h2>
      <p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
    </div>
    <div id="sub">
      <h3>fuga</h3>
      <p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
      <h3>piyo</h3>
      <p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
    </div>
    <div id="footer">フッタフッタフッタフッタフッタフッタフッタ</div>
  </div>
</body>
</html>

こんな感じのHTMLがあると仮定します。

CSSサンプル

body, h1, h2, h3, p {
  margin: 0;
  padding: 0;
}
h1 {
  font-size: 2em;
  padding-top: 10px;
}
h2 {
  font-size: 1.7em;
  padding-top: 10px;
}
h3 {
  font-size: 1.5em;
  padding-top: 10px;
}
p {
  padding-top: 10px;
  padding-bottom: 10px;
}
body {
  /* 基本のfont-size */
  font-size: 100%;
  margin-left: 10px;
}
#container {
  width: 60em;
}
#main {
  width: 45em;
  float: right;
}
#sub {
  width: 15em;
  float: left;
}
#main p {
  font-size: 1.3em;
  padding-left: 10px;
}
#sub p {
  font-size: 1em;
  padding-left: 10px;
}
#footer {
  clear: both;
}

HTMLサンプルにこのCSSを適用すると仮定します。

JavaScriptサンプル

/*
  MDCより
  https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/map
*/
if (!Array.prototype.map) {
  Array.prototype.map = function(fun /*, thisp*/) {
    var len = this.length >>> 0;
    if (typeof fun != "function") {
      throw new TypeError();
    }
    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++) {
      if (i in this) {
        res[i] = fun.call(thisp, this[i], i, this);
      }
    }
    return res;
  };
}

function elasticImages(images, fontSize) {
  var width = 0;
  var height = 0;
  Array.prototype.map.call(images, function(img) {
    (function() {
      if (img && img.width && img.height) {
        width = img.width;
        height = img.height;
      }
      else {
        setTimeout(arguments.callee, 0);
      }
    })();
  });
  (function() {
    if (width > 0 && height > 0) {
      Array.prototype.map.call(images, function(img) {
        return [img, img.width, img.height];
      }).map(function(img) {
        img[1] = parseInt(img[1]/fontSize*1000)/1000;
        img[2] = parseInt(img[2]/fontSize*1000)/1000;
        return img;
      }).map(function(img) {
        img[0].style.width = img[1] + 'em';
        img[0].style.height = img[2] + 'em';
      });
    }
    else {
      setTimeout(arguments.callee, 0);
    }
  })();
};

var images = document.getElementsByTagName('img');
elasticImages(images, 16);

最後にJavaScriptです。

DCLのタイミングで読み込むか、body要素の終了タグ直前にscriptタグとともに挿入する必要があります。

JavaScriptの解説

冒頭のmapメソッドの定義は、mapメソッド使いたい厨なので、Mozilla様から拝借させていただきました。

ところどころ再帰がありますが、これはWebkitベースのブラウザが、画像の読み込み後にしかwidth属性とheight属性の値を取れなくて、onloadとか挟むのはマジメンドクサイと思ったので、再帰で処理待ちしてます。

elasticImages関数は第1引数にimgノードリスト、第2引数にデフォルトフォントサイズを指定します。今は大抵のブラウザのデフォルトフォントサイズが16pxになってるみたいですね。

古いブラウザはそうでもないみたいなので、ブラウザ判定して変数か何かに保存しておけばいいと思います。

あと、ページズーム機能があるブラウザにとっては、この処理を入れることで、微妙に本来の画像の幅と高さに一致しないケースがあると思うので、これもブラウザ判定して、ページズーム機能のないブラウザだけで実行するようにすると親切かなと思います。

サンプル

Elasticサンプル

まとめ

あんまり実用性ないかもしれませんが、頑張りました。