任意のオブジェクト (ID で指定する) までヌルヌルと動く JavaScript を考えよう。どうして「ヌルヌル」という動作条件が出てくるのかといえば、単にジャンプするだけなら HTML のアンカーを使えば良いからである。例えば、
<a href="#hoge">Go HOGE</a>
<p id="Hoge"><a name="hoge"></a>HOGE HOGE</p>
と記述すればよろしい。しかしそれでは面白くない。ここで JavaScript を使って作りたいのは、「Go HOGE」をクリックすると id="Hoge"
まで滑らかにスクロールするスクリプトである。
このスクリプトを組むにあたって必要な値は以下の通り。
objX
, objY
)scrollX
, scrollY
)docW
, docH
)winW
, winH
)1. は bytefx というライブラリによって取得するのが簡単である。
objX = bytefx.$position(document.getElementById(ID)).x;
objY = bytefx.$position(document.getElementById(ID)).y;
2. の値は、次のうちのどれかである(ブラウザ、表示モードによって異なる)。
document.documentElement.scrollLeft、document.documentElement.scrollTop
document.body.scrollLeft、document.body.scrollTop
window.scrollLeft、window.scrollTop
これらを全て取得し、最大値を scrollX
, scrollY
に代入する。
3. の値は、次のどちらかである。
document.documentElement.scrollWidth、document.documentElement.scrollHeight
document.body.scrollWidth、document.body.scrollHeight
両方を取得し、大きい方を docW
, docH
に代入する。
4. の値は、次のどれかである。
document.documentElement.clientWidth、document.documentElement.clientHeight
document.body.clientWidth、document.body.clientHeight
window.innerWidth、window.innerHeight
やはり全て取得し、最小値を 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
, objY
が maxX
, maxY
を越えているとき、つまり最大までスクロールしても対象オブジェクトがウィンドウの一番上に来ない場合、スクリプトは永遠にスクロールしようとする。
maxX
, maxY
はドキュメントの大きさ (docW
, docH
) から、ウィンドウの表示領域 (winW
, winH
) を引いた値である。objX
, objY
が maxX
, 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
, averageY
が objX
, objY
と一致するようにするためである(一致させないと、スクリプトが止まらない)。
Safari 2、Firefox 2、Opera 9 で動作確認。IE 7 では永遠にスクロールし続けた。