IE6 向け position:fixed + スクロール追尾型の要素固定表示の方法を考えてみた

更新履歴

2010-01-21
スクロール時のガタつきをはじめとする諸問題等を解決した精度の高い fixed を使いたい場合はこちらをご覧ください。
position:fixed とは?

結構常識っぽいですがつい最近までその存在を知りませんでした。

絶対位置への配置となるのはabsoluteと同じですが、スクロールしても位置が固定されたままとなります。実際にはブラウザのサポートが遅れているようです

HTML クイックリファレンス

absolute 要素の場合、先祖に relative 要素があると、そこからの相対位置に配置されますが、fixed 要素の場合、先祖の relative の有無に関わらず、常に表示ウィンドウ枠に対しての絶対位置に配置されるようです。

サンプルページ(IE6 では正しく表示されません)


Ajax 普及以前、いろいろ調べて Javascript で苦労しながら書いた記憶がありますが CSS のみで簡単にできてしまいます。
ですが、残念ながら例のごとく IE6 では未対応のようです。
私の仕事である IE6 オンリーなイントラ向け Webアプリ開発には活かせません。

せめて普通にposition:fixedぐらいは使わせて欲しい。
常識的に考えて欲しいだけなんです!

彼氏がIE6を使ってた。別れたい…

…というわけで IE6 での position:fixed の実現方法を調べてみました。

IE6 での position:fixed の実現方法

擬似フレームで position:fixed

擬似フレームとは、本物のフレームのように、文書の特定の領域に別の文書を表示させているように見せる技術です。

擬似フレームとは - しらぎくさいと

擬似フレームっていう言葉自体は知らなかったのですが、position : fixed を実現させるために最近使いました。
私のやってる方法としては、html 要素 のスクロールバーを隠した上で擬似フレームで画面全体を覆い、その上に position:absolute で要素をのせて固定表示させるという方法です。
CSS のみで実現できますが、マークアップが面倒なので、html,bodyの CSSプロパティ をコピーした擬似フレーム(div.html,div.body) を生成するjQueryプラグインを作成して使用してます。
11/20修正
先日の coliss さんのプラグイン紹介記事で紹介されてたサイトの中にも、同じような方法で擬似フレームを使用して position : fixed を実現してるとこがありました。

piroBox
画像ギャラリーにも対応した画像拡大スクリプト

jQueryのプラグイン33+1選 -2008年11月 - coliss

全然方法が違いました。piroBox の記述方法の方がシンプルでお勧めです。記述方法はTHE HAM MEDIA さんの記事で紹介されてます。

position:fixed

実はこれを使ったことなかったのですが、
使いたい場面が出てきたので調べてみた。

ところがこのfixedはIE6でバグがあって使えない。
使うには下記のような設定が必要なようなので
そのCSSを載せておく。

CSSのpositionのまとめ - THE HAM MEDIA
Javascriptで position:fixed

position : absoluteさせた要素に対し、スクロール量分位置をずらして表示させることで、擬似的に固定表示させます。
先の擬似フレームとちがって、スクロールイベントが発生する対度、表示位置を移動させるのでちらつきが発生してしまいます。

IE独自機能の expression を使用するとちらつきを抑えることができるようです。


JS のライブラリもあるようです。

Fixed positioningはIEで固定配置つまり、position:fixedが利用可能になるjsライブラリです。

IEでposition:fixedを再現するFixed positioning - to-R

擬似 fixed 処理は、IE6 が無くなれば不要になる技術ですし、上のような JS ライブラリも既にあるので、今回は、エントリタイトルにもある「スクロール追尾型の要素固定表示」機能(youmosさんwrapScrollみたいなやつ)を付加した fixed 処理を jQuery で考えてみたいと思います。

jQuery で position:fixed

普通の position :fixed

基準座標を保持しておき、スクロールイベントにて基準座標+スクロール量で表示位置をずらします。
absolute を fixed の代替で使用するので、先祖要素に relative 要素がいると表示ウィンドウ枠に対する絶対位置が取れないので、事前に relative 要素の外(弟)に移動しておきます。
基準座標は、parseIntで単位を消して取得してるだけなので、px 以外の単位が指定してあると位置がずれるかもしれません。
スクロールイベントは、$j(window).scroll でひろい、移動量は、$j(document).scrollTop()、scrollLeft() でひろいます。

jQuery(function($j){
    var fixed = function(fix){
        fix.basePos = {
            top : parseInt(fix.css('top'))||0,
            left : parseInt(fix.css('left'))||0
        }
        fix.parents().each(function(){
            var o=$j(this);
            if(o.css('position')=='relative')o.after(fix)
        })
        $j(window).scroll(function(){
            fix.css({
                top : $j(document).scrollTop()+fix.basePos.top,
                left : $j(document).scrollLeft()+fix.basePos.left
            })
        })
    }
    $j('#fix1,#fix2').each(function(){
        fixed($j(this)) 
    })
})

サンプルページ

追尾型の position:fixed

スクロールイベントでいきなり座標を変えずに、setTimeout と animate メソッドで、時間差をもうけた後、追尾させてみます。
スクロールイベント発生間隔が、0.1秒以下だったら前回分のタイマー起動をキャンセルし、新たに移動先を設定しタイマーをセットします。

jQuery(function($j){
    var fixed = function(fix){
        fix.basePos = {
            top : parseInt(fix.css('top'))||0,
            left : parseInt(fix.css('left'))||0
        }
        fix.parents().each(function(){
            var o=$j(this);
            if(o.css('position')=='relative')o.after(fix)
        })
        $j(window).scroll(function(){
            fix.nextPos = {
                top : $j(document).scrollTop()+fix.basePos.top,
                left : $j(document).scrollLeft()+fix.basePos.left
            }
            if(fix.timer)clearTimeout(fix.timer)
            fix.timer = setTimeout(function(){
                fix.animate(fix.nextPos)
            },100)
        })
    }

    $j('#fix1,#fix2').each(function(){
        fixed($j(this)) 
    })
})

サンプルページ

プラグイン化してみる

positionFixed() メソッド

まずはノーマルな fixed から。
追尾型 fixed の定義で継承できるように、prototype ベースで定義します。
ブラウザが IE6 以下以外の場合は、普通にposition:fixed を適用します。

(function($j){
    $j.positionFixed = function(el){
        $j(el).each(function(){
            new fixed(this)
        })
        return el;                  
    }
    $j.fn.positionFixed = function(){
        return $j.positionFixed(this)
    }
    var fixed = $j.positionFixed.impl = function(el){
        this.target = $j(el).css('position','fixed')
        if(!this.ie6)return;
        this.bindEvent();
    }
    $j.extend(fixed.prototype,{
        ie6 : $.browser.msie && $.browser.version < 7.0,
        bindEvent : function(){
            var target=this.target;
            target
            .css('position','absolute')
            .basePos = {
                top: parseInt(target.css('top')) || 0,
                left: parseInt(target.css('left')) || 0
            }
            target.parents().each(function(){
                var o = $j(this);
                if (o.css('position') == 'relative') 
                    o.after(target)
            })
            $j(window).scroll(this.scrollEvent());
        },
        scrollEvent : function(){
            var target=this.target;
            return function(){
                target.css({
                    top: $j(document).scrollTop() + target.basePos.top,
                    left: $j(document).scrollLeft() + target.basePos.left
                })
            }
        }
    })
})(jQuery)

//実行
jQuery(function($j){
    $j('#fix1,#fix2').positionFixed()
})

サンプルページ

delayFixed() メソッド

追尾型の fixed。
positionFixed を継承し、scrollEvent メソッドを override させます。

(function($j){
    $j.delayFixed = function(el,opt){
        $j(el).each(function(){
            new delayFixed(this,opt)
        })
        return el;                  
    }
    $j.fn.delayFixed = function(opt){
        return $j.delayFixed(this,opt)
    }
    var delayFixed = $j.delayFixed.impl = function(el,opt){
        this.opt=$j.extend({delay:100},opt);
        this.target = $j(el)
        this.bindEvent();
    }
    $j.extend(delayFixed.prototype,$j.positionFixed.impl.prototype,{
        scrollEvent : function(){
            var o=this;
            var target=o.target;
            return function(){
                target.nextPos = {
                    top : $j(document).scrollTop()+target.basePos.top,
                    left : $j(document).scrollLeft()+target.basePos.left
                }
                if(target.timer)clearTimeout(target.timer)
                target.timer = setTimeout(function(){
                    target.animate(target.nextPos)
                },o.opt.delay)
            }
        }
    })
})(jQuery)

//実行
jQuery(function($j){
    $j('#fix1,#fix2').delayFixed()
})

サンプルページ

ダウンロード

こちらからどうぞ。