Sticky sidebar: stick to bottom when scrolling down, top when scrolling up

I have been looking for some time now for a solution to my sticky sidebar problem. I have a specific idea of how I would like it to act; effectively, I would like it to stick to the bottom as you scroll down, and then as soon as you scroll back up I would like it to stick to the top, in a fluid motion (no jumping). I am unable to find an example of what I am trying to achieve, so I have created an image that I hope will illustrate the point clearer:

Sticky sidebar: stick to bottom when scrolling down, top when scrolling up

  1. Sidebar sits under the header.
  2. As you scroll down the sidebar remains level with the content of the page so that you can scroll through both sidebar and content.
  3. Reach the bottom of the sidebar, the sidebar sticks to the bottom of the viewport (most plugins only allow for sticking to top, some that allow for sticking to bottom don't allow for both).
  4. Reach the bottom, sidebar sits above the footer.
  5. As you scroll back up, the sidebar stays level with the content so you can scroll through the content and sidebar again.
  6. Reach the top of the sidebar, the sidebar sticks to the top of the viewport.
  7. Reach the top and the sidebar sits back below the header.

I hope this is enough information. I have created a jsfiddle to test any plugins/scripts, which I have reset for this question: http://jsfiddle.net/jslucas/yr9gV/2/ .

69182 次浏览

我也在找同样的东西。显然,我需要搜索一些晦涩的术语,只是为了找到一个类似的问题与图形。结果正是我想要的。我找不到任何插件,所以我决定自己做。希望有人能看到并改进它。

下面是我正在使用的一个简单的 html 示例。

<div id="main">
<div class="col-1">
</div>
<div class="col-2">
<div class="side-wrapper">
sidebar content
</div>
</div>
</div>

下面是我做的 jQuery:

var lastScrollPos = $(window).scrollTop();
var originalPos = $('.side-wrapper').offset().top;
if ($('.col-2').css('float') != 'none') {
$(window).scroll(function(){
var rectbtfadPos = $('.rectbtfad').offset().top + $('.rectbtfad').height();
// scroll up direction
if ( lastScrollPos > $(window).scrollTop() ) {
// unstick if scrolling the opposite direction so content will scroll with user
if ($('.side-wrapper').css('position') == 'fixed') {
$('.side-wrapper').css({
'position': 'absolute',
'top': $('.side-wrapper').offset().top + 'px',
'bottom': 'auto'
});
}
// if has reached the original position, return to relative positioning
if ( ($(window).scrollTop() + $('#masthead').height()) < originalPos ) {
$('.side-wrapper').css({
'position': 'relative',
'top': 'auto',
'bottom': 'auto'
});
}
// sticky to top if scroll past top of sidebar
else if ( ($(window).scrollTop() + $('#masthead').height()) < $('.side-wrapper').offset().top && $('.side-wrapper').css('position') == 'absolute' ) {
$('.side-wrapper').css({
'position': 'fixed',
'top': 15 + $('#masthead').height() + 'px', // padding to compensate for sticky header
'bottom': 'auto'
});
}
}
// scroll down
else {
// unstick if scrolling the opposite direction so content will scroll with user
if ($('.side-wrapper').css('position') == 'fixed') {
$('.side-wrapper').css({
'position': 'absolute',
'top': $('.side-wrapper').offset().top + 'px',
'bottom': 'auto'
});
}
// check if rectbtfad (bottom most element) has reached the bottom
if ( ($(window).scrollTop() + $(window).height()) > rectbtfadPos && $('.side-wrapper').css('position') != 'fixed' ) {
$('.side-wrapper').css({
'width': $('.col-2').width(),
'position': 'fixed',
'bottom': '0',
'top': 'auto'
});
}
}
// set last scroll position to determine if scrolling up or down
lastScrollPos = $(window).scrollTop();


});
}

一些注意事项:

  • .rectbtfad is the bottom most element in my sidebar
  • 我使用我的 # 报头的高度,因为它是一个粘贴的头部,所以它需要 补偿一下
  • 有一个二氧化碳浮子的检查,因为我使用的是响应式设计,不希望这个激活在较小的屏幕上

If anyone can refine this a bit more that'd be great.

Thanks for the great graphic. I was also looking for a solution to this challenge!

不幸的是,这里发布的另一个答案并没有满足要求 # 5,该要求要求能够顺利地滚动回到侧边栏。

我创建了一个实现所有需求的小提琴: http://jsfiddle.net/bN4qu/5/

需要实现的核心逻辑是:

If scrolling up OR the element is shorter than viewport Then
Set top of element to top of viewport If scrolled above top of element
If scrolling down then
Set bottom of element at bottom of viewport If scrolled past bottom of element

在小提琴中,我使用 CSS3转换来移动目标元素,所以它在 IE < 9中不起作用。不过,使用不同的方法,其逻辑是合理的。

此外,我修改了你的小提琴,使粘性侧边栏有一个渐变的背景。这有助于表明正确的行为正在被展示出来。

我希望这对某人有用!

下面是如何实现这一点的一个例子:

JavaScript:

$(function() {


var $window = $(window);
var lastScrollTop = $window.scrollTop();
var wasScrollingDown = true;


var $sidebar = $("#sidebar");
if ($sidebar.length > 0) {


var initialSidebarTop = $sidebar.position().top;


$window.scroll(function(event) {


var windowHeight = $window.height();
var sidebarHeight = $sidebar.outerHeight();


var scrollTop = $window.scrollTop();
var scrollBottom = scrollTop + windowHeight;


var sidebarTop = $sidebar.position().top;
var sidebarBottom = sidebarTop + sidebarHeight;


var heightDelta = Math.abs(windowHeight - sidebarHeight);
var scrollDelta = lastScrollTop - scrollTop;


var isScrollingDown = (scrollTop > lastScrollTop);
var isWindowLarger = (windowHeight > sidebarHeight);


if ((isWindowLarger && scrollTop > initialSidebarTop) || (!isWindowLarger && scrollTop > initialSidebarTop + heightDelta)) {
$sidebar.addClass('fixed');
} else if (!isScrollingDown && scrollTop <= initialSidebarTop) {
$sidebar.removeClass('fixed');
}


var dragBottomDown = (sidebarBottom <= scrollBottom && isScrollingDown);
var dragTopUp = (sidebarTop >= scrollTop && !isScrollingDown);


if (dragBottomDown) {
if (isWindowLarger) {
$sidebar.css('top', 0);
} else {
$sidebar.css('top', -heightDelta);
}
} else if (dragTopUp) {
$sidebar.css('top', 0);
} else if ($sidebar.hasClass('fixed')) {
var currentTop = parseInt($sidebar.css('top'), 10);


var minTop = -heightDelta;
var scrolledTop = currentTop + scrollDelta;


var isPageAtBottom = (scrollTop + windowHeight >= $(document).height());
var newTop = (isPageAtBottom) ? minTop : scrolledTop;


$sidebar.css('top', newTop);
}


lastScrollTop = scrollTop;
wasScrollingDown = isScrollingDown;
});
}
});

CSS:

#sidebar {
width: 180px;
padding: 10px;
background: red;
float: right;
}


.fixed {
position: fixed;
right: 50%;
margin-right: -50%;
}

演示: < a href = “ http://jsfiddle.net/ryanmaxwell/25qae/”rel = “ noReferrer”> http://jsfiddle.net/ryanmaxwell/25qae/

这在所有场景中都能正常工作,并且在 IE 中也得到了很好的支持。

+ 1 到非常漂亮的图像。

I know it's an old question, but I casually found the same question posted by you in Forum.jquery.com and one answer there (by@tucker973), suggested one nice library to make this and wanted to share it here.

@leafo把它叫做 便利贴

这里有我准备的一个非常基本的示例的代码和一个工作演示来查看结果。

/*!
* Sticky-kit
* A jQuery plugin for making smart sticky elements
*
* Source: http://leafo.net/sticky-kit/
*/


$(function() {
$(".sidebar").stick_in_parent({
offset_top: 10
});
});
* {
font-size: 10px;
color: #333;
box-sizing: border-box;
}
.wrapper,
.header,
.main,
.footer {
padding: 10px;
position: relative;
}
.wrapper {
border: 1px solid #333;
background-color: #f5f5f5;
padding: 10px;
}
.header {
background-color: #6289AE;
margin-bottom: 10px;
height: 100px;
}
.sidebar {
position: absolute;
padding: 10px;
background-color: #ccc;
height: 300px;
width: 100px;
float: left;
}
.main {
background-color: #ccc;
height: 600px;
margin-left: 110px;
}
.footer {
background-color: #6289AE;
margin-top: 10px;
height: 250px;
}
.top {
position: absolute;
top: 10px;
}
.bottom {
position: absolute;
bottom: 10px;
}
.clear {
clear: both;
float: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://leafo.net/sticky-kit/src/jquery.sticky-kit.js"></script>
<div class="wrapper">
<div class="header"> <a class="top">header top</a>
<a class="bottom">header bottom</a>


</div>
<div class="content">
<div class="sidebar"> <a class="top">sidebar top</a>
<a class="bottom">sidebar bottom</a>


</div>
<div class="main"> <a class="top">main top</a>
<a class="bottom">main bottom</a>


</div>
<div class="clear"></div>
</div>
<div class="footer"> <a class="top">footer top</a>
<a class="bottom">footer bottom</a>


</div>
</div>

当然,所有的荣誉都归于插件的创建者,我做这个例子只是为了在这里展示它。我需要完成相同的结果,你后,发现这个插件非常有用。

function fixMe(id) {
var e = $(id);
var lastScrollTop = 0;
var firstOffset = e.offset().top;
var lastA = e.offset().top;
var isFixed = false;
$(window).scroll(function(event){
if (isFixed) {
return;
}
var a = e.offset().top;
var b = e.height();
var c = $(window).height();
var d = $(window).scrollTop();
if (b <= c - a) {
e.css({position: "fixed"});
isFixed = true;
return;
}
if (d > lastScrollTop){ // scroll down
if (e.css("position") != "fixed" && c + d >= a + b) {
e.css({position: "fixed", bottom: 0, top: "auto"});
}
if (a - d >= firstOffset) {
e.css({position: "absolute", bottom: "auto", top: lastA});
}
} else { // scroll up
if (a - d >= firstOffset) {
if (e.css("position") != "fixed") {
e.css({position: "fixed", bottom: "auto", top: firstOffset});
}
} else {
if (e.css("position") != "absolute") {
e.css({position: "absolute", bottom: "auto", top: lastA});
}
}
}
lastScrollTop = d;
lastA = a;
});
}


fixMe("#stick");

工作示例: < a href = “ https://jsfiddle.net/L7xoopst/6/”rel = “ nofollow”> https://jsfiddle.net/l7xoopst/6/

Wordpress 存储库中有一个相对不为人知的插件,叫做 WP Sticky Sidebar。这个插件可以完全满足你的需求(粘贴边栏: 向下滚动时粘贴到底部,向上滚动时粘贴到顶部)

样品双向粘边栏。

如果有人需要一个不基于 jQuery 的轻量级解决方案,我邀请您熟悉以下代码: GitHub 上的双向 Sticky-Sidebar。

//aside selector
const aside = document.querySelector('[data-sticky="true"]'),
//varibles
startScroll = 0;
var endScroll = window.innerHeight - aside.offsetHeight -500,
currPos = window.scrollY;
screenHeight = window.innerHeight,
asideHeight = aside.offsetHeight;
aside.style.top = startScroll + 'px';
//check height screen and aside on resize
window.addEventListener('resize', ()=>{
screenHeight = window.innerHeight;
asideHeight = aside.offsetHeight;
});
//main function
document.addEventListener('scroll', () => {
endScroll = window.innerHeight - aside.offsetHeight;
let asideTop = parseInt(aside.style.top.replace('px;', ''));
if(asideHeight>screenHeight){
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;
}, {
capture: true,
passive: true
});
body{
padding: 0 20px;
}
#content {
height: 2000px;
}
header {
width: 100%;
height: 150px;
background: #aaa;
}
main {
float: left;
width: 65%;
height: 100%;
background: #444;
}
aside {
float: right;
width: 30%;
position: sticky;
top: 0px;
background: #777;
}
li {
height: 50px;
}
footer {
width: 100%;
height: 300px;
background: #555;
position: relative;
bottom: 0;
}
<!DOCTYPE html>
<head>
<link href="/src/style.css" rel="preload" as="style"/>
</head>
<body>
<header>Header</header>
<div id="content">
<main>Content</main>
<aside data-sticky="true">
<lu>
<li>Top</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>sidebar</li>
<li>Bottom</li>
</lu>
</aside>
</div>
<footer>Footer</footer>
<script src='/src/script.js' async></script>
</body>
</html>

You can also use Sticky Sidebar JS 插件 for the same effect you are wanting . It has a small and simple documentation on "How to use". I also wanted the similar scrolling effect and it did work pretty nicely.

Https://abouolia.github.io/sticky-sidebar/

香草 JS 选项!

经过一段时间的想做这与香草 JS,我终于破解了它。它当然可以做一些清理,但它的工作!

  const sidebar = document.querySelector('#sidebar');


let lastScrollTop = 0;
let scrollingDown;
let isAbsolute = false;


let absolutePosition = 0;
let windowTop;
let sidebarTop;
let windowBottom;
let sidebarBottom;


function checkScrollDirection() {
if (lastScrollTop <= window.scrollY) {
scrollingDown = true
} else {
scrollingDown = false
}
lastScrollTop = window.scrollY;
}


function fixit(pos,top,bottom,isAb) {


sidebar.style.position = pos;
sidebar.style.top = top;
sidebar.style.bottom = bottom;
isAbsolute = isAb;
}


function scrolling() {
//optional width check
if (window.innerHeight <= sidebar.offsetHeight && window.innerWidth > 996) {
checkScrollDirection();
windowTop = window.scrollY;
sidebarTop = sidebar.offsetTop;
windowBottom = window.scrollY + window.innerHeight;
sidebarBottom = sidebar.offsetHeight + sidebar.offsetTop;


if(!scrollingDown && windowTop <= sidebarTop) {
//fixToTop
fixit("fixed",0,"unset",false)
}


if(scrollingDown && windowBottom >= sidebarBottom) {
//fixToBottom
fixit("fixed","unset",0,false)
}


if((!isAbsolute && windowTop > sidebarTop) || !isAbsolute && windowBottom < sidebarBottom) {
//fixInPlace
let absolutePosition = (windowTop + sidebar.offsetTop) + "px";
fixit("absolute",absolutePosition,"unset",true)
}
}
}


window.addEventListener('scroll', scrolling);