これでできる! クロスブラウザJavaScript入門

第20回JavaScriptによるスタイルの操作

こんにちは、前回はCSSOM View Moduleという仕様の解説がメインでしたので、今回はそのAPIを使ったサンプルを見てみましょう。

ダイアログの表示

ウェブアプリでよく使われるインタフェースとして、ページを覆い被せるように(動的に)コンテンツを表示する方法があります。決まった名前はないようですが、モーダルダイアログ・モードレスダイアログや、オーバーレイなどの名前で呼ばれることが多いようです。

なお、モーダルとモードレスの違いは、前面に出てきたコンテンツの後ろにある元のコンテンツを操作可能か否かという点で分けられるようです。操作できない場合はモーダル、操作できる場合はモードレスです。

さて、このモーダルダイアログは画面中央に配置するのが一般的です。この画面中央という配置が意外に簡単ではないことは多くの方がご存知のことと思います。

まずはCSSだけで処理するアプローチを見ていきましょう。表示する要素のサイズが固定で、IE 6非対応という条件ならば、CSSだけで実現可能です。

CSSで画面中央に配置(サイズ固定・IE 6非対応)
.outer{
  position:fixed;
  top:50%;
  left:50%;
}
.inner{
  position:relative;
  top:-250px;
  left:-250px;
  width:500px;
  height:500px;
  background:#ccc;
}

入れ子になった要素を用意して、外側を画面中心に置き、内側を自分自身のサイズの半分だけ左上に戻してあげれば、その要素は画面中央に配置されます。

IE 6はposition:fixedに対応していないため、JavaScriptでサポートする必要があります。まず簡単な方法としてscroll時に位置を調整する方法を実装してみましょう。

IE 6用にスクロール位置で調整
if (!window.XMLHttpRequest && window.ActiveXObject){
  div.style.position = 'absolute';
  var root = document.documentElement;
  window.attachEvent('onscroll', function(evt){
    div.style.top = root.scrollTop + root.clientHeight / 2 + 'px';
  });
}

IE 6かどうかの判定はXMLHttpRequestが存在するかどうかで判定します第12回のようにIE 6向けにXMLHttpRequestを実装していた場合、この判定に失敗するので注意が必要です⁠⁠。

IE 6のときはpositionをabsoluteにして、スクロール量に画面のサイズの半分を足すことで、fixedのtop:50%相当の位置に合わせています。本来なら横スクロールバーも想定するべきですが、今回は省略しています。

これで一応はIE 6でもposition:fixed相当が実現できました。しかし、実際にスクロールしてみると、スクロールに対して要素の位置がずれることに気がつくと思います。IE 6だから仕方がないといえばそれまでですが、折角なのでこの問題の解決策を紹介します。

スクロール時のちらつきをなくすには、IEのCSS独自拡張である expression といくつかのテクニックを使用します。まず、expressionを定義するCSSです。

expressionによる位置指定
.IE6POSITION_FIXED{
  behavior:expression(
    this.style.top = document.documentElement.scrollTop +
                     document.documentElement.clientHeight / 2
  );
}

behavior:expressionの中はJavaScriptとして解釈されてCSSの描画に合わせて実行されます。描画のたびにJavaScriptが実行されるので、負荷が高い点に注意が必要です。

このCSSがIE 6だけに適用されるように、JavaScriptでclass名を操作します。このとき、body要素の backgroundAttachment をfixedにします。fixedでなくとも動作はしますが、ちらつきは残ってしまいます。また、backgroundImageはなにか指定が必要なので、リクエストが発生しないと思われる url(about:blank) を指定しています。

IE 6用の処理
if (!window.XMLHttpRequest && window.ActiveXObject){
  div.style.position = 'absolute';
  div.className += ' IE6POSITION_FIXED';
  document.body.style.backgroundImage = 'url(about:blank)';
  document.body.style.backgroundAttachment = 'fixed';
}

これでIE 6でもスムーズなposition:fixedが実現できました。しかし、当然重くなる上に、html要素、body要素にbackgroundAttachmentがfixedでない背景画像を設置することができなくなってしまうなどの問題があるので、この方法を使用するかどうかはそういった問題を含めて判断する必要があります。

オーバーレイの表示

続いて、前面に表示した要素以外を半透明化させてみましょう。まずは、画面を覆う方法を考えます。これもIE 6以外ならCSSだけで簡単に実現できます。

HTMLの骨組み
<div class="outer">
  <div class="background_inner">
  </div>
  <div class="content">
  </div>
</div>

まず入れ物となるdivを用意し、その中に背景用のdivとコンテンツ用のdivを用意します。背景用のdivを用意するのは、このdivをopacity(IEではfilter)によって半透明にするためです。なお、IE 6~8以外ではbackgroundに対してrgba形式の色指定で半透明指定ができるので、余計なdivを作らずに半透明な背景を実現できます。

divに対して、position:fixedとtop・left・bottom・rightそれぞれに0pxを指定します。これにより、画面全体を覆うことができます。

CSSで画面を覆う(IE 6非対応)
.outer{
  width:100%;
  height:100%;
  position:fixed;
  top:0px;
  left:0px;
  bottom:0px;
  right:0px;
}
.background_inner{
  background:#000;
  width:100%;
  height:100%;
  position:absolute;
  top:0px;
  left:0px;
  bottom:0px;
  right:0px;
  opacity:0.6;
  filter:alpha(opacity=60);
}
.content{
  position:relative;
}

さて、IE 6はposition:fixedの問題に加えて、top・left・bottom・rightに0pxを指定しても高さが100%にならないという問題もあります。また、filterという重い処理も入るため、スクロール時のちらつきが大きくなってしまいます。そのため、単純にonscrollで位置を調整する方法では次のとおり今ひとつな状態になっていまいます。

オーバーレイをスクロールで配置
button.onclick = function(){
  document.body.appendChild(div); // body直下に移動
  div.style.display = 'block';
};
div.onclick = function(){
  div.style.display = 'none';
};
if (!window.XMLHttpRequest && window.ActiveXObject){
  div.style.position = 'absolute';
  var root = document.documentElement;
  window.attachEvent('onscroll', function(evt){
    div.style.top = root.scrollTop + 'px';
    div.style.height = root.clientHeight + 'px';
  });
}

そこで、画面全体を半透明のレイヤーで覆ってしまい、コンテンツ部分だけをスクロールに合わせて移動するようにしてみます。半透明のレイヤーがページ全体の高さになるので、ページによっては重くなってしまう点に注意が必要です。

オーバーレイを全画面に配置
button.onclick = function(){
 document.body.appendChild(div);
 if (ie6) { // IE 6のとき、画面全体の高さに
   div.style.height = root.scrollHeight + 'px';
 }
 div.style.display = 'block';
};
div.onclick = function(){
 div.style.display = 'none';
};
var ie6 = false;
if (!window.XMLHttpRequest && window.ActiveXObject){
 ie6 = true;
 div.style.position = 'absolute';
 var root = document.documentElement;
 var content = div.children[1];
 window.attachEvent('onscroll', function(evt){
   // contentだけをスクロールに追従
   content.style.top = root.scrollTop + 'px';
 });
}

もしくは、expressionを使ってtop・left・width・heightをすべて指定する方法も有効です。こちらはほかのブラウザでのposition:fixed相当になるというメリットがあります(前述の方法はIE 6だけ要素の位置関係が変わってしまうので、別の問題が発生するリスクが高くなります⁠⁠。

expressionによるposition:fixedのエミュレート
.IE6POSITION_FIXED2{
  behavior:expression(
    this.style.top = document.documentElement.scrollTop,
    this.style.left = document.documentElement.scrollLeft,
    this.style.width = document.documentElement.clientWidth,
    this.style.height = document.documentElement.clientHeight
  );
}

まとめ

今回は画面を覆う方法のIE 6対応を中心に扱いました。クロスブラウザらしい内容ではありますが、expressionはIE 8では廃止されていたりと、近い将来不要になると思われるテクニックでもあるのも事実です。

次回も引き続きCSSに絡んだJavaScriptのサンプルを見ていきたいと思います。

おすすめ記事

記事・ニュース一覧