开发者

Sidebar that follows scroll, but scrolls self, if taller than viewport

(Hey, first post from a longtime lurker :)

I've built a simple sidebar that does the 'absolute-to-fixed' trick to stay on screen, but would like to take in account scenarios, where the sidebar is higher than the viewport.

So I came up with this idea. It all starts as in above:

  • On page load, the sidebar is drawn at starting location, some distance from viewport top.
  • When the user scrolls the page, the sidebar moves with the content
  • If sidebar fits the viewport vertically, it fixes to th开发者_JS百科e top

But here it gets more dynamic:

  • If sidebar is taller than the viewport, it continues to scroll with the content until the bottom of the sidebar is reached, and it fixes there. The top of the sidebar scrolls beyond top of viewport.

  • When the user scrolls back towards page top, the sidebar moves with the content until the top of the sidebar is reached, and it fixes there. The bottom of the sidebar scrolls beyond the bottom of the viewport.

This way the sidebar reacts to scrolling as usual, while sticking around close enough to find on long pages.

Any pointers to examples, or jQuery- friendly code snippets/guidelines would be much appreciated.


I had this exact idea for a project, here's a example of how to implement it

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


The behaviour you would like to achieve is called 'affix'. Twitter's Bootstrap front-end framework has a javascript component that can do what you would like to get. Please check the section about the affix component. In fact, there you can also see a demonstration of the desired behaviour, the menu in the left sidebar is affixed.

You can define functions for the offset parameter of the affix init call, so it can be dynamically determined when to pin/unpin the element. To see an example in context, check the source of the above linked page, specifically look for application.js, it contains the setup of the affix that you can see on the left side. Here you can see the init code taken out of context:

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

It is probably also worth to check in the page sources the docs.css stylesheet, which contains some positioning and styling for the affixed element. Checking this might solve your problem, or at least could give you and idea.


You could use Sticky Sidebar https://abouolia.github.io/sticky-sidebar/#examples

You want the "Scrollable Sticky Element" behaviour See example here https://abouolia.github.io/sticky-sidebar/examples/scrollable-element.html


Since none of those examples suited me well, I did this and I like to share:

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;
}

It scrolls down smoothly when element hits bottom and scrolls up as well when top is hit.


Something like this might work. I haven't tested it but in theory it looks good.

$(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'
            });
        }
    });
});


I needed this exact functionality for a site, and this is what I came up with, it seems to work very nice... I know this post is old, but I assume some people may have an interest in this anyway;)

(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);


You also asked for examples; One example I encounter on a regular basis is the Facebook right column of ads. It is taller than the viewport, and scrolls past the first or second ad and stops on the third. I believe this is defined by the location of the third ad as opposed to the overall height of the sidebar, but it is a working example.

http://facebook.com

screenhots: http://i.imgur.com/dM3OZ.jpg / top of page http://i.imgur.com/SxEZO.jpg / further down


This is very efficient and universal code in pure 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>


Juste made a fork of @Ryan JsFiddle without jQuery (vanilla JS):

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
})
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜