【问题标题】:Sidebar that follows scroll, but scrolls self, if taller than viewport跟随滚动的侧边栏,但如果高于视口,则会自行滚动
【发布时间】:2011-05-19 09:58:57
【问题描述】:

(嘿,来自长期潜伏者的第一篇文章:)

我已经构建了一个简单的侧边栏,它使用“absolute-to-fixed”技巧来保持在屏幕上,但我想考虑侧边栏高于视口的场景。

所以我想出了这个主意。一切如上开始:

  • 页面加载时,边栏在起始位置绘制,距离视口顶部有一段距离。
  • 当用户滚动页面时,侧边栏会随着内容移动
  • 如果侧边栏垂直适合视口,它会固定在顶部

但在这里它变得更加动态:

  • 如果侧边栏高于视口,它继续随着内容滚动,直到到达侧边栏的底部,并固定在那里。侧边栏的顶部滚动到视口顶部之外。

  • 当用户向后滚动到页面顶部时,侧边栏会随着内容移动,直到到达侧边栏的顶部,并固定在那里。侧边栏的底部滚动到视口底部之外。

这样,侧边栏会像往常一样对滚动做出反应,同时保持足够近的距离以便在长页面上找到。

任何指向示例的指针,或 jQuery 友好的代码 sn-ps/指南将不胜感激。

【问题讨论】:

    标签: javascript jquery css


    【解决方案1】:

    Juste 在没有 jQuery(原版 JS)的情况下创建了 @Ryan JsFiddle 的一个分支:

    https://jsfiddle.net/Kalane/8w09orvg/6/

    function getOffset(el) {
      const win = el.ownerDocument.defaultView
      const rect = el.getBoundingClientRect()
      return {
        top: rect.top + win.pageYOffset,
        left: rect.left + win.pageXOffset
      }
    }
    
    function getPosition(el) {
      const offset = this.getOffset(el)
      const parentOffset = this.getOffset(el.parentNode)
      return {
        top: offset.top - parentOffset.top,
        left: offset.left - parentOffset.left,
      }
    }
    
    
    let lastScrollTop = document.documentElement.scrollTop
    let wasScrollingDown = true
    const sidebar = document.querySelector('#sidebar')
    const initialSidebarTop = getPosition(sidebar).top
    
    
    window.addEventListener('scroll', () => {
      const windowHeight = window.innerHeight
      const sidebarHeight = Math.ceil(sidebar.getBoundingClientRect().height)
    
      const scrollTop = document.documentElement.scrollTop
      const scrollBottom = scrollTop + windowHeight
    
      const sidebarTop = getPosition(sidebar).top
      const sidebarBottom = sidebarTop + sidebarHeight
    
      const heightDelta = Math.abs(windowHeight - sidebarHeight)
      const scrollDelta = lastScrollTop - scrollTop
    
      const isScrollingDown = (scrollTop > lastScrollTop)
      const isWindowLarger = (windowHeight > sidebarHeight)
    
      if ((isWindowLarger && scrollTop > initialSidebarTop) || (!isWindowLarger && scrollTop > initialSidebarTop + heightDelta)) {
        sidebar.classList.add('fixed')
      } else if (!isScrollingDown && scrollTop <= initialSidebarTop) {
        sidebar.classList.remove('fixed')
      }
    
      const dragBottomDown = (sidebarBottom <= scrollBottom && isScrollingDown)
      const dragTopUp = (sidebarTop >= scrollTop && !isScrollingDown)
    
      if (dragBottomDown) {
        if (isWindowLarger) {
          sidebar.style.top = 0
        } else {
          sidebar.style.top = `${-heightDelta}px`
        }
      } else if (dragTopUp) {
        sidebar.style.top = 0
      } else if (sidebar.classList.contains('fixed')) {
        const currentTop = parseInt(sidebar.style.top, 10)
    
        const minTop = -heightDelta
        const scrolledTop = currentTop + scrollDelta
    
        const isPageAtBottom = scrollTop + windowHeight >= document.body.scrollHeight
        const newTop = (isPageAtBottom) ? minTop : scrolledTop
    
        sidebar.style.top = `${newTop}px`
      }
    
      lastScrollTop = scrollTop
      wasScrollingDown = isScrollingDown
    })
    

    【讨论】:

      【解决方案2】:

      这是纯 javascript 中非常高效且通用的代码:

      //aside is your sidebar selector
      var aside = document.querySelector('aside');
      //sticky sidebar on scrolling
      var startScroll = aside.offsetTop;
      var endScroll = window.innerHeight - aside.offsetHeight;
      var currPos = window.scrollY;
      document.addEventListener('scroll', () => {
          var asideTop = parseInt(aside.style.top.replace('px;', ''));
          if (window.scrollY < currPos) {
              //scroll up
              if (asideTop < startScroll) {
                  aside.style.top = (asideTop + currPos - window.scrollY) + 'px';
              } else if (asideTop > startScroll && asideTop != startScroll) {
                  aside.style.top = startScroll + 'px';
              }
          } else {
              //scroll down
              if (asideTop > endScroll) {
                  aside.style.top = (asideTop + currPos - window.scrollY) + 'px';
              } else if (asideTop < (endScroll) && asideTop != endScroll) {
                  aside.style.top = endScroll + 'px';
              }
          }
          currPos = window.scrollY;
      });
      //this code will bring to you sidebar on refresh page
      function asideToMe() {
          setTimeout(() => {
              aside.style.top = startScroll + 'px';
          }, 300);
      }
      asideToMe();
      body{
        padding: 0 20px;
      }
      #content {
        height: 1200px;
      }
      header {
        width: 100%;
        height: 150px;
        background: #aaa;
      }
      main {
        float: left;
        width: 65%;
        height: 100%;
        background: linear-gradient(180deg, rgba(255,255,255,1) 0%, rgba(0,0,0,1) 100%);
      }
      aside {
        float: right;
        width: 30%;
        height: 800px;
        position: sticky;
        top: 100px;
        background: linear-gradient(0deg, rgba(2,0,36,1) 0%, rgba(22,153,66,1) 51%, rgba(167,255,0,1) 100%);
      }
      footer {
        width: 100%;
        height: 70px;
        background: #555;
      }
      <body>
        <header>Header</header>
        <div id="content">
          <main>Content</main>
          <aside>Sidebar</aside>
        </div>
        <footer>Footer</footer>
      </body>

      【讨论】:

        【解决方案3】:

        您可以使用粘性侧边栏 https://abouolia.github.io/sticky-sidebar/#examples

        您想要“可滚动的粘性元素”行为 请参阅此处的示例https://abouolia.github.io/sticky-sidebar/examples/scrollable-element.html

        【讨论】:

          【解决方案4】:

          因为这些例子都不适合我,所以我做了这个,我想分享:

          JS

          $(function () {
              var element = $('.right-container');
              var originalY = element.offset().top;
              var lastOffset = $(document).scrollTop();
              var diffToBottom = element.height() - $(window).height();
          
              $(window).on('scroll', function (event) {
                  var scrollTop = $(window).scrollTop();        
                  var currentOffset = $(document).scrollTop();
                  var isScrollingDown = currentOffset > lastOffset;
          
                  if (scrollTop > (diffToBottom + element.height())) {
                      if (!element.hasClass('relative')) {
                          element.css('top', scrollTop - (originalY + diffToBottom) + 'px');
                          element.addClass('relative');
                      }
          
                      if (isScrollingDown) {
                          var newTop = scrollTop < lastOffset ? 0 : scrollTop - (originalY + diffToBottom);
                      }
                      else if (scrollTop < element.offset().top) {
                          var newTop = scrollTop - (originalY);
                      }
          
                      element.stop(false, false).animate({
                          top: newTop
                      }, 300);
                  }
                  else {
                      if (scrollTop < originalY) {
                          element.removeClass('relative');
                      }
          
                      element.stop(false, false).animate({
                          top: scrollTop - (originalY)
                      }, 300);
                  }
          
                  lastOffset = currentOffset;
              });
          });
          

          CSS

          .relative {
              position: relative;
          }
          

          当元素到达底部时它会平滑地向下滚动,当元素到达顶部时它也会向上滚动。

          【讨论】:

            【解决方案5】:

            我对一个项目有这个确切的想法,这里有一个如何实现它的例子

            http://jsfiddle.net/ryanmaxwell/25QaE/

            【讨论】:

            • 谢谢,这真的很有帮助,谢谢!我想我会在这个小提琴的第 10 行分享我需要使用$sidebar.offset() 而不是$sidebar.position(),我想是因为我在侧边栏上方有很多浮动元素。
            • 任何想法如何实现页脚?
            • 您能看看我的侧边栏并尝试向上滚动吗?为什么会这样跳? klimantavicius.com/charmy/search-results.html
            【解决方案6】:

            您还要求提供示例;我经常遇到的一个例子是 Facebook 右栏的广告。它比视口高,滚动经过第一个或第二个广告并停在第三个广告上。我相信这是由第三个广告的位置而不是侧边栏的整体高度来定义的,但这是一个可行的示例。

            http://facebook.com

            截图: http://i.imgur.com/dM3OZ.jpg / 页面顶部 http://i.imgur.com/SxEZO.jpg/再往下

            【讨论】:

              【解决方案7】:

              您想要实现的行为称为“附加”。 Twitter 的 Bootstrap 前端框架有一个 javascript 组件,可以做你想做的事情。请查看section about the affix component。实际上,您还可以在此处看到所需行为的演示,左侧边栏中的菜单已粘贴。

              您可以为词缀初始化调用的偏移参数定义函数,因此可以动态确定何时固定/取消固定元素。要查看上下文中的示例,请查看上述链接页面的来源,特别是查找application.js,它包含您可以在左侧看到的词缀的设置。在这里你可以看到脱离上下文的初始化代码:

              // side bar
              $('.bs-docs-sidenav').affix({
                offset: {
                  top: function () { return $window.width() <= 980 ? 290 : 210 }
                , bottom: 270
                }
              })
              

              可能还值得在页面源中检查docs.css 样式表,其中包含附加元素的一些定位和样式。检查这可能会解决您的问题,或者至少可以给您和想法。

              【讨论】:

                【解决方案8】:

                我需要一个网站的确切功能,这就是我想出的,它似乎工作得很好......我知道这篇文章很旧,但我想有些人可能对此感兴趣; )

                (function($){
                    $(document).ready(function(){
                    sidebar_offset=$('#header').height()+10;
                    $(window).scroll(function(){
                        var sidebar=$('#primary');
                    if($(this).scrollTop()<sidebar_offset){
                        var height=$(window).height()-(sidebar_offset-$(this).scrollTop());
                        css={height:height,marginTop:0};
                        sidebar[0].scrollTop=0;
                    }else{
                        css.height=$(this).height();
                        if($(this).scrollTop()>sidebar_offset){
                            if(this.lastTop>$(this).scrollTop()){
                                sidebar[0].scrollTop=sidebar[0].scrollTop-(this.lastTop-$(this).scrollTop());
                            }else{
                                sidebar[0].scrollTop=sidebar[0].scrollTop+($(this).scrollTop()-this.lastTop);
                            }
                
                            css.marginTop=($(this).scrollTop()-sidebar_offset)+'px';
                        }else{
                            sidebar[0].scrollTop=0;
                        }
                     }
                     sidebar.css(css);
                     this.lastTop=$(this).scrollTop();
                    });
                })(jQuery);
                

                【讨论】:

                  【解决方案9】:

                  这样的事情可能会奏效。我还没有测试过,但理论上它看起来不错。

                  $(function(){
                      var standardPosition = $('whatYouWantToScroll').offset().top;
                      // Cache the standard position of the scrolling element
                      $(window).scroll(function() {
                          if ($(this).scrollTop() < standardPosition) {
                              // if scroll pos is higher than the top of the element
                              $('whatYouWantToScroll').css({
                                  position: 'relative',
                                  top: '0px'
                              });
                          } else if ($(window).scrollTop() > $('somethingToReferenceOnTheBottom').offset().top-($('whatYouWantToScroll').height())) {
                              // if scroll position is lower than the top offset of the bottom reference
                              // + the element that is scrolling height
                              $('whatYouWantToScroll').css({
                                  position: 'absolute',
                                  top: $('somethingToReferenceOnTheBottom').offset().top-($('whatYouWantToScroll').height())
                              });
                          } else {
                              // otherwise we're somewhere inbetween, fixed scrolling.
                              $('whatYouWantToScroll').css({
                                  position: 'fixed'
                              });
                          }
                      });
                  });
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-12-01
                    • 1970-01-01
                    • 2012-05-26
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-09-23
                    相关资源
                    最近更新 更多