- JavaScript: 任意のオブジェクトまでスクロールする

どういうスクリプトか

任意のオブジェクト (ID で指定する) までヌルヌルと動く JavaScript を考えよう。どうして「ヌルヌル」という動作条件が出てくるのかといえば、単にジャンプするだけなら HTML のアンカーを使えば良いからである。例えば、

<a href="#hoge">Go HOGE</a>

<p id="Hoge"><a name="hoge"></a>HOGE HOGE</p>

と記述すればよろしい。しかしそれでは面白くない。ここで JavaScript を使って作りたいのは、「Go HOGE」をクリックすると id="Hoge" まで滑らかにスクロールするスクリプトである。

必要な値

このスクリプトを組むにあたって必要な値は以下の通り。

  1. オブジェクトの x, y 座標 (objX, objY)
  2. スクロールされた(ウィンドウの左上に表示されているドキュメントの)x, y 座標 (scrollX, scrollY)
  3. ドキュメントの幅、高さ (docW, docH)
  4. ウィンドウの幅、高さ (winW, winH)

オブジェクトの x, y 座標

1. は bytefx というライブラリによって取得するのが簡単である。

スクロールされた x, y 座標

2. の値は、次のうちのどれかである(ブラウザ、表示モードによって異なる)。

これらを全て取得し、最大値を scrollX, scrollY に代入する。

ドキュメントの幅、高さ

3. の値は、次のどちらかである。

両方を取得し、大きい方を docW, docH に代入する。

ウィンドウの幅、高さ

4. の値は、次のどれかである。

やはり全て取得し、最小値を winW, winH に代入する。

画面座標系のライブラリ

2.、3.、4. 値を得るための関数 getScroll() を考える。

function getScroll () {
 var Obj = new Object();
 var scrollX1 = scrollX2 = scrollX3 = scrollY1 = scrollY2 = scrollY3 = docW1 = docW2 = docH1 = docH2 = winW1 = winW2 = winW3 = winH1 = winH2 = winH3 = 0;
 if (document.documentElement) {
  scrollX1 = document.documentElement.scrollLeft || 0;
  scrollY1 = document.documentElement.scrollTop || 0;
  docW1 = document.documentElement.scrollWidth || 0;
  docH1 = document.documentElement.scrollHeight || 0;
  winW1 = document.documentElement.clientWidth;
  winH1 = document.documentElement.clientHeight;
 }
 if (document.body) {
  scrollX2 = document.body.scrollLeft || 0;
  scrollY2 = document.body.scrollTop || 0;
  docW2 = document.body.scrollWidth || 0;
  docH2 = document.body.scrollHeight || 0;
  winW2 = document.body.clientWidth;
  winH2 = document.body.clientHeight;
 }
 scrollX3 = window.scrollX || 0;
 scrollY3 = window.scrollY || 0;
 winW3 = window.innerWidth;
 winH3 = window.innerHeight;
 Obj.scrollX = Math.max(scrollX1, Math.max(scrollX2, scrollX3));
 Obj.scrollY = Math.max(scrollY1, Math.max(scrollY2, scrollY3));
 Obj.docW = Math.max(docW1, docW2);
 Obj.docH = Math.max(docH1, docH2);
 Obj.winW = Math.min(winW1, Math.min(winW2, winW3));
 Obj.winH = Math.min(winH1, Math.min(winH2, winH3));
 return Obj;
}

これで必要な値が出揃った。

スクロールの実際

現在の座標 (scrollX, scrollY) から、目的の座標 (objX, objY) までスクロールさせる。注意しなければならないのは、スクロールすることができる最大の値 (maxX, maxY) が決まっているということだ。objX, objYmaxX, maxY を越えているとき、つまり最大までスクロールしても対象オブジェクトがウィンドウの一番上に来ない場合、スクリプトは永遠にスクロールしようとする。

maxX, maxY はドキュメントの大きさ (docW, docH) から、ウィンドウの表示領域 (winW, winH) を引いた値である。objX, objYmaxX, maxY を越えているようなケースでは、目的の座標を maxX, maxY にする必要がある。

スクロールのための準備

対象オブジェクトにスクロールするための準備は次のようなルーチンになる。

var objX = objY = 0;
function goPageAnchor (ID) {
 var Obj = getScroll();
 var maxX = Obj.docW - Obj.winW;
 var maxY = Obj.docH - Obj.winH;
 objX = bytefx.$position(document.getElementById(ID)).x;
 objY = bytefx.$position(document.getElementById(ID)).y;
 if (objX > maxX) { objX = maxX; }
 if (objY > maxY) { objY = maxY; }
 goPageAnchor2();
}

アンカーリンクをクリックしたときに goPageAnchor(ID) が動くよう、HTML の方で記述しておく。冒頭の例を改造すれば次のようになる。

<a href="#" onClick="goPageAnchor('Hoge')">Go HOGE</a>

<p id="Hoge">HOGE HOGE</p>

スクロールさせてみる

実際にスクロールをする部分のルーチン goPageAnchor2() は次のようになる。

function goPageAnchor2 () {
 var scrollX = getScroll().scrollX;
 var scrollY = getScroll().scrollY;
 var averageX = (objX + scrollX)/2;
 var averageY = (objY + scrollY)/2;
 if (objX > scrollX) {
  averageX = Math.ceil(averageX);
 } else if (obxX < scrollX) {
  averageX = Math.floor(averageX);
 }
 if (objY > scrollY) {
  averageY = Math.ceil(averageY);
 } else if (objY < scrollY) {
  averageY = Math.floor(averageY);
 }
 window.scrollTo(averageX, averageY);
 if (averageX != objX || averageY != objY) {
  window.setTimeout("goPageAnchor2()", 25);
 }
}

現在の座標と目標の座標の中間点 (averageX, averageY) を取り、そこへ移動する。現在の座標と目標の座標が一致するまで、この動作が繰り返される。移動距離が一回毎に半分となるので、「最初は速く、次第に遅く」というヌルヌルしたスクロールが演出される。一回の移動量、動作の間隔を調節すれば、望むヌルヌル感が得られる。

スクロールの方向によって averageX, averageY の値を切り捨てたり切り上げたりしているのは、最終的に averageX, averageYobjX, objY と一致するようにするためである(一致させないと、スクリプトが止まらない)。

動作確認

Safari 2、Firefox 2、Opera 9 で動作確認。IE 7 では永遠にスクロールし続けた。

関連