よくメニューがスクロールと一緒にくっついてくる、あれです。

サンプル

Element.offsetTopというプロパティ

この機能を作るにあたっては移動する要素のbody上の位置を取得することが必要です。offsetTop(並びにoffsetLeftoffsetHeightoffsetWidthoffsetParent)は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は明示的なクラスがないのでどうもよくわからん。ホントは

1
var sc = new ScrollChaser('side')

とか、引数を渡すだけでいいようにしたいんだけどな。隠蔽できるようにがんばる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//
// 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);
}