web-dev-qa-db-ja.com

CSSとjQueryを使用したInstagramのiPhoneアプリのように、「プッシュ」するためのスティッキーヘッダーを取得する

Instagramアプリには、新しいヘッダーの代わりに現在のヘッダーをプッシュするNiceスティッキーヘッダーがあります。 Android向けにネイティブでこれを行う方法に関する優れたチュートリアルを見つけましたが、JavaScriptとCSSを使用してそれを実現したいと考えています。

新しいヘッダーに切り替えるためにヘッダーを取得できましたが、Instagramが行う方法を模倣する方法を見つけることができません。どんな助けも大歓迎です。

*Edit:ヘッダーを取得することができました指摘されたコメントのCjとしてウェイポイントを使用してスクロールするときのページの上部。 ( ウェイポイントへのリンク )。私が抱えている主な問題は、instagramがiPhone用モバイルアプリで使用する「プッシュアップ」効果を得ることです。例にリンクしますが、以前に使用されたのを見たことがありません。*

**編集2:@Chrisが提供したcodepenの一部を使用して、ヘッダーを固定することができました。次に、.slideUpエフェクトを追加しました。私の問題は、次のヘッダーに到達したときにのみ.slideUp効果が発生するようにすることです。現在、スクロール時にエフェクトがアクティブになります。

コードは次のとおりです。

(function() {
function stickyTitles(stickies) {
    this.load = function() {
        stickies.each(function(){
            var thisSticky = jQuery(this);
            jQuery.data(thisSticky[0], 'pos', thisSticky.offset().top);
        });
    }
    this.scroll = function() {      
        stickies.each(function(){           
            var thisSticky = jQuery(this),          
                pos = jQuery.data(thisSticky[0], 'pos');
            if (pos <= jQuery(window).scrollTop()) {
                thisSticky.addClass("fixed");
                // added this 
                 $(".followMeBar:parent").slideUp();

            } else {
                thisSticky.removeClass("fixed");
            }
        });         
    }
}
jQuery(document).ready(function(){
    var newStickies = new stickyTitles(jQuery(".followMeBar"));
    newStickies.load();
    jQuery(window).on("scroll", function() {
        newStickies.scroll();

    }); 
});

})();

70
Ryan Rich

これに対する迅速なまたは簡単な答えはありませんが、少し創造的な手間をかけることで、同じ機能をエミュレートできます。

必要なのは、ページ上の位置にヒットしたときに前のアイテムが押し上げられ、新しいアイテムが修正されるように、特定、ループ、およびセットアップできる一連の要素です。 jQueryのoffset().topメソッドを使用して要素の初期位置を取得し、後で参照できるようにdataタグに保存する必要があります。次に、スクロールしながら残りの部分が計算されます。

これでうまくいくはずです:

var stickyHeaders = (function() {

  var $window = $(window),
      $stickies;

  var load = function(stickies) {

    if (typeof stickies === "object" && stickies instanceof jQuery && stickies.length > 0) {

      $stickies = stickies.each(function() {

        var $thisSticky = $(this).wrap('<div class="followWrap" />');
  
        $thisSticky
            .data('originalPosition', $thisSticky.offset().top)
            .data('originalHeight', $thisSticky.outerHeight())
              .parent()
              .height($thisSticky.outerHeight());                         
      });

      $window.off("scroll.stickies").on("scroll.stickies", function() {
                  _whenScrolling();             
      });
    }
  };

  var _whenScrolling = function() {

    $stickies.each(function(i) {                        

      var $thisSticky = $(this),
          $stickyPosition = $thisSticky.data('originalPosition');

      if ($stickyPosition <= $window.scrollTop()) {        
        
        var $nextSticky = $stickies.eq(i + 1),
            $nextStickyPosition = $nextSticky.data('originalPosition') - $thisSticky.data('originalHeight');

        $thisSticky.addClass("fixed");

        if ($nextSticky.length > 0 && $thisSticky.offset().top >= $nextStickyPosition) {

          $thisSticky.addClass("absolute").css("top", $nextStickyPosition);
        }

      } else {
        
        var $prevSticky = $stickies.eq(i - 1);

        $thisSticky.removeClass("fixed");

        if ($prevSticky.length > 0 && $window.scrollTop() <= $thisSticky.data('originalPosition') - $thisSticky.data('originalHeight')) {

          $prevSticky.removeClass("absolute").removeAttr("style");
        }
      }
    });
  };

  return {
    load: load
  };
})();

$(function() {
  stickyHeaders.load($(".followMeBar"));
});
.followMeBar {
  background: #999;
  padding: 10px 20px;
  position: relative;
  z-index: 1;
  color: #fff;
}
.followMeBar.fixed {
  position: fixed;
  top: 0;
  width: 100%;
  box-sizing: border-box;
  z-index: 0;
}
.followMeBar.fixed.absolute {
  position: absolute;
}
/* For aesthetics only */

body {
  margin: 0;
  font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="followMeBar">A</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">B</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">C</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">D</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">E</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">F</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">G</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">H</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">I</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">J</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">K</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">L</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">M</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">N</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">O</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">P</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">Q</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">R</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">S</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">T</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">U</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">V</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">W</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">X</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">Y</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">Z</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

CSSのみのバージョンは次のとおりです。

「何?!CSSのみのバージョンがあったら、私はそれをすべて経験しました!」と言う前に。いくつかのブラウザでのみ動作します。 例えばFirefoxでこれを試してください:

.sticky {
  position: -webkit-sticky;
  position: -moz-sticky;
  position: -o-sticky;
  position: -ms-sticky;
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  display: block;
  z-index: 1;
  background: #999;
  color: #fff;
  padding: 10px 20px;
}

/* For aesthetics only */
body {
  margin: 0;
  font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
}
<div data-lorem="p">
  <span class="sticky">a</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">b</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">c</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">d</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">e</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">f</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">g</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">h</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>

http://caniuse.com/#feat=css-sticky

129
Chris Spittles

まず、@ Chris Spittlesのすばらしい回答に感謝します。

修正されたバージョンを作成しました。修正されたバージョンでは、各固定要素をラップする必要がなくなりました。固定位置を使用する代わりに、相対位置を変更するだけです。

var stickyHeaders = (function() {

    var $stickies;

    var load = function(stickies, target) {

        if (typeof stickies === "object" && stickies instanceof jQuery && stickies.length > 0) {

            $stickies = stickies.each(function() {

                var $thisSticky = $(this);

                $thisSticky
                    .data('originalPosition', $thisSticky.offset().top)
                    .data('originalHeight', $thisSticky.outerHeight());               
            });

            target.off("scroll.stickies").on("scroll.stickies", function(event) {
                 _whenScrolling(event);     
            });
        }
    };

    var _whenScrolling = function(event) {

        var $scrollTop = $(event.currentTarget).scrollTop();

        $stickies.each(function(i) {            

            var $thisSticky = $(this),
                $stickyPosition = $thisSticky.data('originalPosition'),
                $newPosition,
                $nextSticky;

            if ($stickyPosition <= $scrollTop) {

                $newPosition = Math.max(0, $scrollTop - $stickyPosition);
                $nextSticky = $stickies.eq(i + 1);

                if($nextSticky.length > 0) {

                    $newPosition = Math.min($newPosition, ($nextSticky.data('originalPosition') -  $stickyPosition) - $thisSticky.data('originalHeight'));
                }

            } else {

                $newPosition = 0;
            }

            $thisSticky.css('transform', 'translateY(' + $newPosition + 'px)');

            //could just as easily use top instead of transform
            //$thisSticky.css('top', $newPosition + 'px');
        });
    };

    return {
        load: load
    };
})();

$(function() {
    stickyHeaders.load($(".followMeBar"), $(window));
});

CSSは次のように簡略化されます。

.followMeBar {
    background: #999;
    padding: 10px 20px;
    position: relative;
    z-index: 1;
    color: #fff;
}

/* For aesthetics only */

body {
    margin: 0;
    font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
}

http://plnkr.co/edit/wk3h40LfBdN1UFtDLZgY?p=preview

また、固定ヘッダーを使用するときにオフセットを設定する方法を示す別の例を次に示します。

http://plnkr.co/edit/8YBqdCIKruVKYRXbZnCp?p=preview

6
james

上記のソリューションを試してみましたが、バグなしで完全に機能するものはほとんどありません(ほとんどがスクロールとブラウザーの互換性に関するバグです)。ちょうどこれを見つけました https://github.com/sarahdayan/feedify そしてそれは私のためにうまく機能しています。

2
webchun

すべてのスティッキーは比較的位置付けられたdivの内部にあるため、Chrisの答えを正確に機能させるのに苦労していました(すべてのヘッダーは相対divの外側にあります)-これに対する答えは.offset()を保存するだけですvar内の相対コンテナdivの.topで、スクリプトの.css( 'top'、value)から減算します。 chrisのバージョンと同様に、最上位の値はドキュメントの最上部から押し出され、正常に機能します。相対divを追加すると、topは代わりにその上部からプッシュされるようになるため、相対divの上のスペースは値に含まれますが、これは望ましくないものです。これが他の人の助けになることを願っています。ジェームス

1
user2668414

@ chris-spittlesが提供したソリューションは大いに役立ちましたが、Vanilla Javascriptソリューションも提供したいと思います。さらに、コンテンツ領域を超えて修正を継続したくない場合に、最後のスティッキーを処理する機能を追加しました。

https://codepen.io/jamigibbs/pen/QZWjBy

const StickySubsections = (function(){
  let el

  return {
    elements: function () {
      return {
        stickies: [...document.querySelectorAll('.followMeBar')]
      }
    },

    init: function () {
      el = this.elements()
      this.load()
    },

    load: function () {
      this.setupStickyWrap()
      window.addEventListener('scroll', () => this.whenScrolling())
    },

    setupStickyWrap: function(){
      el.stickies.forEach((sticky, i) => {
        const stickyWrap = this.addWrap(sticky, 'sticky-wrap')
        const heightToTop = sticky.getBoundingClientRect().top + window.scrollY
        const outerHeight = sticky.offsetHeight

        stickyWrap.parentElement.id = `sticky-content-${i}`
        sticky.setAttribute('data-originalPosition', heightToTop)
        sticky.setAttribute('data-originalHeight', outerHeight)

        stickyWrap.style.height = outerHeight + 'px'
      })
    },

    addWrap: function(el, className, wrap = 'div') {
      const wrapper = document.createElement(wrap)
      wrapper.classList.add(className)
      el.parentNode.insertBefore(wrapper, el)
      wrapper.appendChild(el)
      return wrapper
    },

    elementExists: function(el){
      return typeof(el) != 'undefined' && el != null
    },

    stickyPosition: function(el){
      return el.getAttribute('data-originalPosition')
    },

    nextStickyPosition: function(current, next){
      return next.getAttribute('data-originalPosition') - current.getAttribute('data-originalHeight')
    },

    scrollingPositionTop: function(el){
      return el.getBoundingClientRect().top + window.scrollY
    },

    offsetTop: function(el){
      return el.getBoundingClientRect().top
    },

    scrollingPositionBottom: function(el){
      return el.getBoundingClientRect().bottom + window.scrollY
    },

    headerPosition: function(){
      return window.scrollY
    },

    bottomSectionHit: function(contentElement, sticky){
      const contentSection = document.getElementById(contentElement)
      const sectionBottom = contentSection.getBoundingClientRect().bottom + window.scrollY
      const stickyPositionScrolling = sticky.getBoundingClientRect().bottom + window.scrollY

      return stickyPositionScrolling >= sectionBottom
    },

    whenScrolling: function() {
      el.stickies.forEach((sticky, i) => {
        const nextSticky = el.stickies[i + 1]
        const prevSticky = el.stickies[i - 1]

        if (this.stickyPosition(sticky) <= this.headerPosition()) {
          sticky.classList.add('fixed')

          if (this.elementExists(nextSticky)) {

            while (this.scrollingPositionBottom(sticky) >= this.nextStickyPosition(sticky, nextSticky) + 50) {
              sticky.classList.add('absolute')
              sticky.style.top = this.nextStickyPosition(sticky, nextSticky)
            }

          // Handle last sticky element
          } else {
            if (this.bottomSectionHit(`sticky-content-${i}`, sticky)) {
              sticky.classList.remove('fixed')
            }
          }

        } else {

          sticky.classList.remove('fixed')

          if (this.elementExists(prevSticky) && window.scrollY <= this.stickyPosition(sticky)){
            prevSticky.classList.remove('absolute')
            prevSticky.removeAttribute('style')
          }
        }
      })
    }

  }
}())

StickySubsections.init()
0
jami0821

スティッキーヘッダーを持つdivの上にコンテンツがある場合、これは常にうまく機能するとは限りません:$thisSticky.css('transform', 'translateY(' + $newPosition+ 'px)');

しかし、この行は//could just as easily use top instead of transform $thisSticky.css('top', $newPosition+ 'px');トリックを行います

0
spnkr

ソリューションの基本バージョン:

CSS

.sectionWrapper{
    background-color: #1E1E1E;
    padding: 10px 20px;
    color: #FF7B05;
    width: 100%
    height: 45px;
    min-height: 45px;

    margin-top: 30px;   
    position: relative;
    z-index: 10;
}
.sectionHeader{
}
.sectionEmbed {
}
.sectionFixed {
    background-color: #1E1E1E;
    padding: 10px 20px;
    color: #FF7B05;
    width: 100%;
    height: 45px;

    position: fixed;
    top: 0;
    left: 0;
    margin: 0;
}

HTML

<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 sectionWrapper sectionEmbed">
    <div class="sectionHeader">Headers here</div>
</div>

JQuery

//check doc is ready
$(function() {
    $(window).on("scroll.sectionWrapper", function() {
        stickSections();
    });
});

//function stickSection
function stickSections()
{
    //detach the listener
    $(window).off("scroll.sectionWrapper");

    $scrollPos = $(window).scrollTop();
    $currentSticky = null;

    $('.sectionWrapper').each(function(){

        $(this).children().removeClass('sectionFixed');     

        if($scrollPos >= ($(this).offset().top - ($(this).height() - $(this).children().height() - 6)) ){
            $currentSticky = $(this);
        }
        else $(this).children().addClass('sectionEmbed');
    });

    //apply the fixed class to our scrollPos match
    if($currentSticky) $currentSticky.children().addClass('sectionFixed');

    //reattach the listener
    $(window).on("scroll.sectionWrapper", function() {
        stickSections();        
    });
}

https://codepen.io/ZombiesByte/pen/EyYwVO

楽しむ :)

0
Dal