よくメニューがスクロールと一緒にくっついてくる、あれです。
Element.offsetTopというプロパティ
この機能を作るにあたっては移動する要素のbody
上の位置を取得することが必要です。offsetTop
(並びにoffsetLeft
、offsetHeight
、offsetWidth
、offsetParent
)はIE5の時に独自に拡張されたプロパティで、他のブラウザも追随して実装したようです。渡りに船のようなプロパティですが、これがブラウザごとにまるで解釈が違うと言うとんでもない厄介者でした。
offsetTop/offsetLeft/offsetParentの闇でかなり掘り下げて検証されているのですが(引用しといて失礼な話ですが、正直な所、じっくり読む気にはなりません・・)、例えば、IE6では指定要素の親要素から何pxオフセット(?)しているかを返しますが、Firefoxではbody
要素からの値を返します。
実は、これはmootoolsなどのJavaScriptライブラリを使うとそのための関数とかあったりします。が、今回は勉強のために自力でやってみますた!!!
コード中のgetTop()という関数がそれです。関数が呼び出されるとbodyに到達するまでtopにoffsetTopを加え続けます。しかし、どうもbodyのmargin指定があるときうまく取得できないんですよねー。ここはbody{ margin: 0 }が前提と言うことで・・IEだとエラーが出て動かない。先にposition="absolute"を指定すればoffsetParentがHTML要素になるのでその方法で回避することにした。→div要素などをスクロールに追随させるjavascript【その2】
IE6はDTDがxhtmlだとbody.onscrollが動かない
attachEvent
で回避。
IE6だとホイールでスクロールしたとき動きが変
FirefoxやSafariだとスクロールバーをドラッグしても、ホイールでスクロールしても、要素がスムースに動くんですが(scrollTop
をターゲットに収束させている)、IE6がこれを無視しやがるのでonmousewheel
イベントを使って直した気になってた。
今見るとonscroll
でもonmousewheel
でもスムースに動く時とぎこちない時があるようなのでonscroll
のみにした。
内容とは直接関係ないけど、JavaScriptでクラスっぽいの作るのって難しいな
ActionScriptでクラス化する書き方はだいぶ分かってきたんだけど、JavaScriptは明示的なクラスがないのでどうもよくわからん。ホントは
var sc = new ScrollChaser('side')
とか、引数を渡すだけでいいようにしたいんだけどな。隠蔽できるようにがんばる。
//
// scrollchaser.js
//
// 2008.2.4
//
var el;
var initTop;
var interval = 200;
//
function setting()
{
el = document.getElementById('side');
initTop = el.offsetTop||getTop( { el:el } );
el.style.position = 'absolute';
try {
window.addEventListener('scroll', scrollEvent, false);
} catch (e) {
window.attachEvent('onscroll',scrollEvent);
}
}
function scrollEvent()
{
var myInt = setInterval(function()
{
var cur = (document.body.scrollTop||document.documentElement.scrollTop)+20;
var myTop = el.offsetTop||getTop( { el:el } );
var d = cur-myTop;
if(Math.abs(d)>1) {
if( cur >= initTop )
{
var y = myTop + d/3;
el.style.top = setPx(y);
} else {
el.style.top = setPx(initTop);
clearInterval(myInt);
}
} else {
el.style.top = setPx(cur);
clearInterval(myInt);
}
},interval);
}
function getTop( args )
{
var el = args.el;
var tagName = el.tagName;
var top = args.top||0;
if(tagName=='body'||tagName=='BODY')
{
return top;
} else {
return arguments.callee( { el:el.offsetParent, top:top += el.offsetTop } );
}
}
function setPx( num )
{
return num+'px';
}
try {
window.addEventListener('load', setting, false);
} catch (e) {
window.attachEvent('onload', setting);
}