スクロールしたらヘッダーの一部だけ固定する

スクロールしたらヘッダーをコンパクトにして固定させるというやつです。CSS の書き方で色んなパターンが出来ます。

これは以前 Qiita に載せていたものを移動させた記事です。

デモ

シンプル 背景画像付き 上下余白 サブヘッダー有 上下で別の動き

JavaScriptは3行

基本 JavaScript の処理はヘッダーにクラスやカスタム属性を付けるだけで十分です。このフラグを使って CSS の書き方で色々調整が出来ます。

$(window).on('scroll', function() {
    $('#header').toggleClass('fixed', $(this).scrollTop() > 50);
});

固定さたい .fixheader が上から 50px の位置にあるので、その分スクロールしたら外側の #header に .fixed を追加します。外側のレイヤーの属性に付けているのは単に .fixheader 以外にもアニメーションを付けられるようにするためなので勿論 .fixheader 自体に付けても body 等につけてもOKです。

ヘッダーの内容にabsolutefixedがはいってもいいように、コンテナ用のレイヤーに必要な分の高さを指定するか、ヘッダーの下の要素にマージンを指定します。これが無いと固定された時に無くなった分がカクっと上がってしまいます。

短いので jQuery 使わずにネイティブに書くとこんな感じでしょうか。クラス操作系はネイティブだと面倒なので属性にしまったほうが処理も早いし楽そうです。classList という便利なプロパティもあるのですけどIE以下略。

window.addEventListener('scroll', function(){
    document.getElementById('header').setAttribute('data-fixed', window.pageYOffset > 50 ? '1' : '0');
}, false);

例1)シンプルに固定

スクロールしたら .fixheader だけを固定します。

#header {
  background-color: #f5f5f5;
  height: 90px;
}
#header .fixheader {
  position: absolute;
  background-color: #f0f0f0;
  width: 100%;
  top: 50px;
}
#header.fixed .fixheader {
  position: fixed;
  top: 0px;
}

例2)ヘッダー全体に背景を使う

例1-1 の場合 .fixheader とそれ以外で背景が分かれているので背景画像をヘッダー全体に使った時に違和感を無くします。

#header {
  height: 90px;
}
#header .fixheader {
  position: absolute;
  background-image: url('../images/bg01.png');
  box-shadow: 0px 1px 1px 0px rgba(0,0,0,.2), 0px -1px 5px 1px rgba(255,255,255,1) inset;
  width: 100%;
  padding-top: 50px;
}
#header.fixed .fixheader {
  position: fixed;
  margin-top: -50px;
}

あらかじめpadding-top: 50px;で上に余分な背景を作っておいて、レイヤーが固定されたら余分な 50px 分margin-top: -50px;で画面外にあげます。

例3)下にもヘッダー内容がある場合

.fixheader より下にもヘッダー内容がある場合の例です。

そのままスクロールすると下の内容が重なってスクロールされるので、#header.fixedになったら消えるようにしておきます。アニメーションとか入れるとチョットいい感じ(•ө•)ノ

#header {
    height: 150px;
    color: #fff;
}
#header a {
    color: #fff;
}
#header .fixheader {
    position: absolute;
    background: rgba(0,0,0,.8);
    box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, .2);
    width: 100%;
    padding-top: 50px;
    padding-bottom: 50px;
    transition: padding-bottom .2s ease 0s;
}
#header.fixed .fixheader {
    position: fixed;
    margin-top: -50px;
    padding-bottom: 0px;
}
#header .header_bottom {
    /* 出てくる時のアニメーション設定 */
    transition: margin-top .4s ease 0s, opacity .4s ease .2s;
    opacity: 1;
}
#header.fixed .header_bottom {
    /* 消える時のアニメーション設定 */
    transition: margin-top 0s ease 0s, opacity .4s ease 0s;
    margin-top: -120px;
    opacity: 0;
}
/* タイトルにもちょっとアニメーション付けてみる */
#header h1 {
    line-height: 40px;
    transform: scale(1) rotate3d(1, 0, 0, -0deg);
    transition: transform 0.6s ease;
}
#header.fixed h1 {
    transform: scale(0.6) rotate3d(1, 0, 0, -360deg);
}

display:none だとアニメーションが付けられないので、margin-top と opacity で擬似的に消しています。

例4)サブヘッダーを回収していく

以下の例は少々 JavaScript の処理を追加していますがサブヘッダーも回収していくネイティブなスクリプトのサンプルも作ってみました。簡易的なパララックスの処理をしています。上の例よりスクロールフック時の位置の比較処理が多めに入るのでサブヘッダーの数が多くなるような場合はスクロールの度ではなくタイマーを使って遅延で処理して、自然に出現するように CSS でアニメーションを付けるといいかもです。

document.addEventListener('DOMContentLoaded', function() {

    // 縮小した時のヘッダーの高さ
    var fixheader_height = 40;
    var window_height;
    var header_pos = 0;
    var subheader_map = [];

    // リサイズイベントで要素の位置を取得する
    function isWindowInit() {
        window_height = document.documentElement.clientHeight;
        var pageY = window.pageYOffset;
        subheader_map = []
        header_pos = document.getElementsByClassName('fixheader')[0].getBoundingClientRect().top + pageY;
        var subheaders = document.getElementsByClassName('subheader');
        for (var idx = 0; idx < subheaders.length; idx++) {
            var elem = subheaders[idx];
            var id = elem.id;
            var el_top = elem.getBoundingClientRect().top + pageY;
            subheader_map.push({
                id: id,
                top: el_top,
            });
        }

        // 画面下から順にソート
        subheader_map.sort(function(a, b) {
            return (a.top < b.top) ? 1 : (a.top > b.top) ? -1 : 0;
        });
    }
    window.addEventListener('resize', isWindowInit, false);
    isWindowInit();


    // スクロールイベントで属性値を変更
    function isFixheader() {
        var sc = window.pageYOffset;
        if (sc > header_pos) {
            document.getElementById('header').setAttribute('data-fixed', '1');
        } else {
            document.getElementById('header').setAttribute('data-fixed', '0');
        }
        var f = false;
        for (var idx in subheader_map) {
            if (sc > subheader_map[idx].top - fixheader_height && !f) {
                document.getElementById(subheader_map[idx].id).setAttribute('data-fixed', '1');
                f = true;
            } else {
                document.getElementById(subheader_map[idx].id).setAttribute('data-fixed', '0');
            }
        }
    }
    window.addEventListener('scroll', isFixheader, false);
    isFixheader();

}, false);
#header {
    background-color: #f5f5f5;
    height: 150px;
}

#header .fixheader {
    z-index: 100;
    position: absolute;
    background: rgba(220,220,220,.8);
    width: 100%;
    top: 80px;
    height: 70px;
    transition: .4s height;
}

#header[data-fixed="1"] .fixheader {
    position: fixed;
    top: 0px;
    height: 40px;
}

.subheader {
    height: 30px;
}

.subheader-body {
    z-index: 99;
    background: rgba(0, 0, 0, .5);
    text-align: center;
    color: #fff;
    line-height: 30px;
}

.subheader[data-fixed="1"] .subheader-body {
    position: fixed;
    top: 40px;
    width: 100%;
}

#header h1 {
    position: absolute;
    margin: 0;
    top: 50%;
    left: 50%;
    width: 180px;
    text-align: center;
    font-size: 30px;
    transform: translate(-50%, -100px) scale(1);
    transition: transform 0.6s ease, left 0.6s ease;
}

#header[data-fixed="1"] h1 {
    transform: translate(-50%, -50%) scale(0.4);
}

.menu {
    display: table;
    margin: 0;
    padding: 0;
    position: relative;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.menu li {
    display: table-cell;
    padding: 5px;
}

.menu li:nth-child(2)::before {
    display: block;
    content: "";
    width: 0px;
    transition: width 0.6s ease;
}

#header[data-fixed="1"] .menu li:nth-child(2)::before {
    width: 100px;
}

アニメーションしなくても死なないのでIE9以下のアニメーションには対応していません。


例5)スクロールの上下で別の動きにする

JavaScript でスクロールのタイプを分類してその属性値を使ってアニメーションを付けます。例では少し細かく4種類に分類しています。上にスクロールした時だけヘッダーメニューを表示させるようにしてみました。

  • data-fixmode="top" - スクロールしていない状態
  • data-fixmode="wait" - スクロールし始めでスクロール量が(n)px以下の状態
  • data-fixmode="up" - 上にスクロールしている状態
  • data-fixmode="down" - 下にスクロールしている状態
document.addEventListener('DOMContentLoaded', function() {

    // どっちにスクロールしているかの確認用に前回のスクロール位置を記憶します
    var sc_past = 0;
    var el_header = document.getElementById('header');

    function isFixheader() {
        var sc = window.pageYOffset;
        // 位置が0ならTOPにいる
        if (sc == 0) {
            el_header.setAttribute('data-fixmode', 'top');
        }
        // 位置が前回より大きく(n)px以下の場合(スクロールし始めに動かさない為)
        else if (sc > sc_past && sc < 200) {
            el_header.setAttribute('data-fixmode', 'wait');
        }
        // 位置が前回より小さければ上にスクロールしている
        else if (sc < sc_past) {
            el_header.setAttribute('data-fixmode', 'up');
        }
        // それ以外なら上にスクロールしている
        else {
            el_header.setAttribute('data-fixmode', 'down');
        }
        sc_past = sc;
    }
    window.addEventListener('scroll', isFixheader, false);
    isFixheader();

}, false);
#header h1 {
    position: absolute;
    width: 100%;
    text-align: center;
    line-height: 60px;
    transition: transform .6s ease;
}

#header:not([data-fixmode="top"]):not([data-fixmode="wait"]) h1 {
    transform: translateY(-100%);
}

#header .fixheader {
    position: fixed;
    background: rgba(240, 240, 240, .85);
    width: 100%;
    height: 100px;
    top: 0px;
    transform: translateY(0%);
    transition: transform .6s ease;
}

#header[data-fixmode="up"] .fixheader {
    transform: translateY(-60px);
}

#header[data-fixmode="down"] .fixheader {
    transform: translateY(-100%);
}

.menu {
    display: table;
    margin: 0;
    padding: 0;
    position: relative;
    top: calc(50% + 20px);
    left: 50%;
    transform: translate(-50%, -50%);
    transition: top .6s ease;
}

#header[data-fixmode="up"] .menu {
    top: calc(50% + 30px);
}

.menu li {
    display: table-cell;
    padding: 0 10px;
    line-height: 38px;
    border-bottom: 2px solid transparent;
    transition: padding .6s ease;
}

#header:not([data-fixmode="top"]) .menu li {
    padding: 0 5px;
}

.menu li:hover {
    border-bottom: 2px solid #2fb9ac;
}
Edited on 2017.03.27 Created on 2015.07.03 Webデザイン JavaScript jQuery