スクロール位置を維持しつつ web ページ全体のスクロールをできないようにする方法 (web ページのスクロール位置固定)

最近は Ajax などによって web ページを動的に変更するという手法も一般的になって、ページの中にさらにサブページのようなものをポップアップする、ということがしばしば行われます。 例えば Facebook の Theater mode のようなものです。

さて、「ページの中にサブページのようなものをポップアップして表示する」 場合、サブページがスクロール可能であれば、もともとのページ全体はスクロール不可能にしておく方がユーザーにとっては使いやすいデザインであると思います。 実際、Facebook でも Theater mode の際には Theater 側にスクロールバーがあるのみで、ページ全体はスクロール不可になります。

本記事では、そのような用途に用いるための 「スクロール位置を維持しつつ web ページ全体のスクロール可否性を変更する関数」 を紹介します。

ページ全体をスクロール不可能にする方法

さて、まずはページ全体をスクロールする方法を説明します。 一般的なブラウザにおいては、表示領域として viewport があります。 ブラウザの描画領域のことだと考えてください。 Web ページ全体のコンテンツが描画される領域よりも viewport が小さい場合は、ページ全体をスクロールする機構が viewport に与えられる、というのが一般的です。

つまり、ページ全体のスクロールを不可にするというのは、すなわち viewport のスクロールを不可にする、ということです。 スクロールの可否に関わる CSS のプロパティといえば overflow ですが、CSS 2.1 勧告の overflow の項 に次のように書かれています。

UAs must apply the 'overflow' property set on the root element to the viewport.

HTML 文書であれば、ルート要素である html 要素に overflow: hidden と設定をすると、それが viewport に適用される、ということです。

つまり、ページ全体をスクロール不可能にする方法は、html 要素に overflow: hidden と設定する ということになります。

スクロール位置がリセットされてしまう

しかし、html 要素に overflow: hidden と設定すると、Firefox 5 などではスクロール位置が元のままではなく、ページ上部に戻ってしまいます。 この挙動が仕様で規定されているものなのか実装依存なのかはよくわかりませんが、現実問題としてスクロール位置がリセットされてしまうのでなんとかする必要があります。

スクロール位置の取得と設定

この問題の対応策として、html 要素に overflow: hidden を設定する前のスクロール位置を予め取得しておいて、overflow: hidden の設定後に元のスクロール位置を設定しなおす、という方法があります。

Web ページ全体の (viewport の?) スクロール位置を取得したり設定したりするメソッドは、CSSOM View Module の ScreenView インターフェイス に定義されています。 まだ Working Draft 段階ですが、FirefoxOperaSafari などのブラウザでは既に実装されています。 ScreenView インターフェイスが実装されている実行環境であれば、次のような JavaScript コードで、ページのスクロール位置を取得できます。

// x 方向のスクロール位置
var xos = document.defaultView.pageXOffset;
// y 方向のスクロール位置
var yos = document.defaultView.pageYOffset;

また、IE 6 以降では、次のコードで同様に取得できます。

// x 方向のスクロール位置
var xos = document.documentElement.scrollLeft;
// y 方向のスクロール位置
var yos = document.documentElement.scrollTop;

ページのスクロール位置の変更は、次のようなコードで実行できます。

// ScreenView インターフェイスが実装されている場合
document.defaultView.scrollTo( xoffset, yoffset );
// IE の場合
window.scrollTo( xoffset, yoffset );

スクロール位置を維持しつつ web ページ全体のスクロール可否性を変更する関数

というわけで、まとめ代わりにスクロール位置を維持しつつ web ページ全体のスクロール可否性を変更する JavaScript 関数をおいておきます。

Firefox 5、Opera 11.50、Safari 5、IE 6, 7, 8, 9 では期待通りに動くことを確認しました。

var setPageScrollable = function setPageScrollable( scrollable ) {
    var dv = window;
    var xOffset, yOffset, de;
    if( document.defaultView ) {
        dv = document.defaultView;
        xOffset = dv.pageXOffset;
        yOffset = dv.pageYOffset;
    } else {
        de = document.documentElement;
        xOffset = de.scrollLeft;
        yOffset = de.scrollTop;
    }
    document.documentElement.style.overflow = ( scrollable ? "auto" : "hidden" );
    dv.scrollTo( xOffset, yOffset );
}

サンプルページを置いておきますのでご覧ください。