粘性侧边栏:向下滚动时固定在底部,向上滚动时固定在顶部

Posted

技术标签:

【中文标题】粘性侧边栏:向下滚动时固定在底部,向上滚动时固定在顶部【英文标题】:Sticky sidebar: stick to bottom when scrolling down, top when scrolling up 【发布时间】:2013-08-21 13:30:37 【问题描述】:

我一直在寻找解决我的粘性侧边栏问题的方法。我对我希望它如何行动有一个具体的想法;实际上,我希望它在您向下滚动时粘在底部,然后一旦您向上滚动,我希望它以流畅的运动(不跳跃)粘在顶部。我找不到我想要达到的目标的示例,所以我创建了一个图像,希望能更清楚地说明这一点:

    边栏位于页眉下方。 向下滚动时,侧边栏与页面内容保持水平,以便您可以滚动浏览侧边栏和内容。 到达侧边栏的底部,侧边栏会粘在视口的底部(大多数插件只允许粘在顶部,一些允许粘在底部的插件不允许两者都允许)。 到达底部,侧边栏位于页脚上方。 当您向上滚动时,边栏与内容保持水平,因此您可以再次滚动浏览内容和边栏。 到达侧边栏的顶部,侧边栏会粘在视口的顶部。 到达顶部,侧边栏位于标题下方。

我希望这是足够的信息。我创建了一个 jsfiddle 来测试任何插件/脚本,我已经为这个问题重置了:http://jsfiddle.net/jslucas/yr9gV/2/.

【问题讨论】:

这是一个很好的问题和很好的说明。就像一个注释(因为我正在寻找其他搜索词):这个确切的行为是在 WordPress 管理界面中实现的。我还没有通过代码,而是在检查发生了什么。似乎侧边栏设置为postition:fixed。然后是一个 JS 滚动事件监听器,它检测侧边栏是否应该随着内容滚动,并将其切换到 position:absolute 并使用 javascript 设置 top。一旦侧边栏停止滚动,它会再次更改为position:fixed,并使用适当的top 不能再编辑我原来的评论了。但这是在 WP 管理界面中实现这一点的代码:core.trac.wordpress.org/changeset/26125(相关赛道票:core.trac.wordpress.org/ticket/19994) 这个问题有 2021 年的解决方案吗? 【参考方案1】:

+1 非常漂亮和说明性的图像。

我知道这是一个老问题,但我偶然发现你在forum.jquery.com 中发布的相同问题和一个答案 (by@tucker973),建议了一个不错的库来做这个并想要在这里分享。

它被 @leafo 称为 sticky-kit

github proyect webpage simple example in jsFiddle (与此处附加的sn-p相同的代码)

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

/*!
 * 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>

当然,所有功劳归插件的创建者所有,我只是做了这个例子来展示它。我需要完成与您相同的结果,并发现此插件非常有用。

【讨论】:

我按照你的建议使用这个小插件,但是在它粘在顶部之后宽度会发生变化。我怎样才能让它保持不变? 嗨@gmo!我正在寻找同样的东西,但是当滚动条比视口长时它不起作用(向上滚动时不会粘在顶部)...... 项目似乎被放弃了。甚至演示页面也不再工作。 @JulesColle - 是的,即使是演示也无法正常工作。还有另一个项目 - abouolia.github.io/sticky-sidebar 试试这个。 @AnoopNaik 看起来不错。我想这个答案需要更多的支持:***.com/a/68115705/296430【参考方案2】:

感谢您提供出色的图形。我也在寻找这个挑战的解决方案!

不幸的是,此处发布的另一个答案没有满足要求 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

另外,我修改了您的小提琴,使粘性侧边栏具有渐变背景。这有助于表明正在表现出正确的行为。

我希望这对某人有用!

【讨论】:

对于任何寻找答案的人来说,Travis 的这个是我迄今为止发现的最完美的一个。谢谢大佬。 一个很好的尝试,当我把它放进去时,它基本上就起作用了,这比我对其他插件所能说的要多:) 性能受到了很大的打击,但我认为这几乎是任何一个给定的非本地粘性实现。 这是一个很好的起点!我将$.css 函数包装在requestAnimationFrame 中,并添加了一个销毁/取消绑定函数,以便在vue/react 等现代前端框架中使用。在那之后性能绝对不是问题! @Cristophe Marois 你能提供一个关于 jsfiddle 的例子吗? 谢谢,但此代码不适用于视口较短(视口高度)的小侧边栏【参考方案3】:

这是一个如何实现的示例:

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

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

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

【讨论】:

看到这个答案并解释***.com/questions/28428327/… @Anoop Naik - 这几乎是我正在寻找的好东西......sticky-kit 不适用于比视口更长的侧边栏,你的作品。但是我想要相反:当我向下滚动时,它会粘在顶部,而在向上滚动时,它会粘在底部......你能帮我解决一下小提琴中的那个小变化吗? @IgorLaszlo 当然,给我一些时间,稍后会更新你...... 这也解释了我的问题:“当具有位置的元素:sticky 被“卡住”并且比视口长时,您只能在滚动到容器底部后才能看到它的内容。这会很酷,如果“卡住”元素随着文档滚动并停止,一旦它到达其底部边缘。如果用户向后滚动,同样的事情会再次发生,但相反。 - 由另一个有同样问题的人写的 (***.com/questions/47618271/…) @Anoop Naik !感谢您的努力,但请允许,我找到了 Sticky jquery 插件来解决我的问题:abouolia.github.io/sticky-sidebar 再次感谢您!【参考方案4】:

两个方向的粘性侧边栏示例。

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

//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>

【讨论】:

【参考方案5】:

你也可以使用Sticky SidebarJS插件来达到你想要的效果。它有一个关于“如何使用”的小而简单的文档。我也想要类似的滚动效果,而且效果很好。

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

【讨论】:

【参考方案6】:

原版 JS 选项!

想用 Vanilla 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);

【讨论】:

【参考方案7】:

我一直在寻找完全相同的东西。显然,我需要搜索一些晦涩的术语才能找到与图形类似的问题。原来这正是我想要的。我找不到任何插件,所以我决定自己制作。希望有人会看到并改进它。

这是我正在使用的快速而肮脏的示例 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 是我侧边栏中最底部的元素 我正在使用 #masthead 的高度,因为它是一个粘性标题,所以它需要 弥补它 检查 col-2 浮动,因为我使用的是响应式设计,不希望在较小的屏幕上激活它

如果有人能再完善一下,那就太好了。

【讨论】:

【参考方案8】:
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");

工作示例: https://jsfiddle.net/L7xoopst/6/

【讨论】:

加一点解释? 如果您更新粘性项目中的高度,这会出现一些问题【参考方案9】:

Wordpress 存储库中有一个相对不为人知的插件,称为 WP Sticky Sidebar。该插件完全符合您的要求(粘性侧边栏:向下滚动时粘在底部,向上滚动时粘在顶部)WP Sticky Sidebar Wordpress 存储库链接:https://wordpress.org/plugins/mystickysidebar/

【讨论】:

感谢您的信息!工作完美。有趣的是,插件特色图片的行为插图图形是相同的:)

以上是关于粘性侧边栏:向下滚动时固定在底部,向上滚动时固定在顶部的主要内容,如果未能解决你的问题,请参考以下文章

React - 向下滚动时向上滑动固定导航栏,向上滚动时向下滑动

双向滚动时隐藏固定菜单

在页脚上重叠的滚动固定侧边栏

滚动 - 单击时无法滚动固定侧边栏中的自定义滚动

类似于原生联系人应用程序的粘性搜索栏和部分标题行为

底部对齐的导航棒滚动到顶部