简体   繁体   中英

How to make a sidebar sticky when scrolling between header and footer with jQuery (without scrolling over them)?

I need a strictly jQuery solution for this problem. This is because I'm using Wordpress and the sidebar widget that I want to make sticky is inside an <aside> element which I cannot make full height.

In the same way that .scrollTop() detects how far down the page I am to make the sidebar widget sticky I need JS detect how far up I am from the bottom of the page to "unstick" the widget via assigning a new fixed position.

I tried to to this with .offset() but I have been unable to make it work so far.

  function stopDiv() {
    var distance = $('.footer').offset().top - $('.widget').offset().top;
    if (distance < 10) {
      $('.widget').css({
        'top': 'auto',
        'bottom': '10px'
      });
    }
  }

As you can see in the snippet below my sidebar scrolls as it should, but I want the sidebar to assume a new fixed position when I reach <10px distance from the footer.

I want the sidebar to assume a new fixed position above the footer until the user scrolls back up.

Edit: The solution suggested below by @Benvc works fine in the snippet, but not on my wordpress site. Here are the console errors I'm getting:

scripts.js:18 Uncaught ReferenceError: s is not defined
    at HTMLDocument.<anonymous> (scripts.js:18)
    at i (jquery.js:2)
    at Object.fireWith [as resolveWith] (jquery.js:2)
    at Function.ready (jquery.js:2)
    at HTMLDocument.K (jquery.js:2)
    at HTMLDocument.s (VM11874 rocket-loader.min.js:1)
    at p (VM11874 rocket-loader.min.js:1)
    at t.simulateStateAfterDeferScriptsActivation (VM11874 rocket-loader.min.js:1)
    at Object.callback (VM11874 rocket-loader.min.js:1)
    at t.run (VM11874 rocket-loader.min.js:1)

  // Fixed Widget function fixDiv() { var $cache = $('.widget'); if ($(window).scrollTop() > 380) $cache.css({ 'position': 'fixed', 'top': '10px', 'right': '30px' }); else $cache.css({ 'position': 'relative', 'top': 'auto', 'right': 'auto' }); } $(window).scroll(fixDiv); fixDiv(); /* My attempt function stopDiv() { var distance = $('.footer').offset().top - $('.widget').offset().top; if (distance < 10) { $('.widget').css({ 'top': 'auto', 'bottom': '10px' }); } } $(window).scroll(stopDiv); stopDiv(); */ 
 @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700'); * { font-family: 'Open Sans'; color: #fff; box-sizing: content-box; } body { padding: 0; margin: 0; } p { margin: 20px; } hr { width: 85%; border-style: solid; } .main-content { width: 100%; display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: 150px auto; grid-template-areas: "nav nav nav nav" "main main main sidebar"; grid-column-gap: 20px; grid-row-gap: 0px; } .nav { grid-area: nav; background-color: #266392; display: grid; grid-template-columns: 1fr 3fr 1fr; } .nav h1 { place-self: center; font-weight: 400; font-size: 40px; grid-column: 2; } .nav i { align-self: center; font-size: 40px; } .main { height: 1500px; width: 98%; justify-self: start; grid-area: main; padding: 10px; float: left; background-color: #e8624c; margin: 10px; } .sidebar-container { height: 900px; width: 300px; justify-self: start; background-color: #209B66; grid-area: sidebar; grid-column: 4; top: 10px; margin: 10px; padding: 20px; display: grid; grid-template-rows: auto; grid-row-gap: 10px; } .sidebar-container>p { display: grid; align-items: start; padding: 0; margin: 0; } .widget { height: 500px; width: 300px; background-color: #E3962F; } .footer { background-color: #333; height: 800px; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <body> <div class="main-content"> <div class="nav"> <h1>Sticky Sidebar Problem</h1> <i class="fa fa-arrow-down" aria-hidden="true"></i> </div> <div class="main"> <p> [Main Content] </p> </div> <div class="sidebar-container"> <p>[Sidebar Container]</p> <div class="widget"> <p> [Widget]</p> </div> </div> </div> <div class="footer"></div> </body> 

We can break this up into 2 different problems. Problem #1, you need the bottom Y-chord of your element (a), and Problem #2 you need the bottom of the page (b). If you have this data, then your answer is simply (b - a).

To help us, let's create a helper function as such:

function getOffset(el) {
  const rect = el.getBoundingClientRect();
  return {
    left: rect.left + window.scrollX,
    right: rect.left - window.scrollX,
    top: rect.top + window.scrollY,
    bottom: rect.bottom -window.scrollY
  };
}

Using this, it should be as simple as getOffset(document.body).bottom - getOffset(yourElement).bottom .

Hope this helps!

(Note that I can't actually test this code now so it may not work out of the box, so use it more as a guide not a copy/paste).

EDIT - jquery solution (original css only answer below):

Based on your question edits that provide additional constraints and make an html / css only solution a bit more difficult, below is a jquery solution using your code with new jquery for the sticky sidebar widget and css to make the sidebar widget position: absolute and right: 30px (that value is arbitrary depending on where exactly you want the widget to sit inside the sidebar). Also, commented out a few other css lines that either weren't doing anything or interfered with the responsiveness of your grid layout (sticky sidebar functionality works with or without those changes, although you may need to adjust the right css of your widget element, including media queries, depending on where your layout ultimately ends up).

 $(function() { const sidebar = $('.sidebar-container'); const widget = $('.widget'); const footer = $('.footer'); const space = 10; // arbitrary value to create space between the window and widget const startTop = sidebar.offset().top + 60; // arbitrary start top position const endTop = footer.offset().top - widget.height() - space; widget.css('top', startTop); $(window).scroll(function() { let windowTop = $(this).scrollTop(); let widgetTop = widget.offset().top; let newTop = startTop; if (widgetTop >= startTop && widgetTop <= endTop) { if (windowTop > startTop - space && windowTop < endTop - space) { newTop = windowTop + space; } else if (windowTop > endTop - space) { newTop = endTop; } widget.stop().animate({ 'top': newTop }); } }); }); 
 @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700'); * { font-family: 'Open Sans'; color: #fff; box-sizing: content-box; } body { padding: 0; margin: 0; } p { margin: 20px; } hr { width: 85%; border-style: solid; } .main-content { width: 100%; display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: 150px auto; grid-template-areas: "nav nav nav nav" "main main main sidebar"; grid-column-gap: 20px; grid-row-gap: 0px; } .nav { grid-area: nav; background-color: #266392; display: grid; grid-template-columns: 1fr 3fr 1fr; } .nav h1 { place-self: center; font-weight: 400; font-size: 40px; grid-column: 2; } .nav i { align-self: center; font-size: 40px; } .main { height: 1500px; /*width: 98%; justify-self: start;*/ grid-area: main; padding: 10px; /*float: left;*/ background-color: #e8624c; margin: 10px; } .sidebar-container { height: 900px; width: 300px; justify-self: end; background-color: #209B66; grid-area: sidebar; grid-column: 4; /*top: 10px;*/ margin: 10px; padding: 20px; display: grid; grid-template-rows: auto; grid-row-gap: 10px; } .sidebar-container>p { display: grid; align-items: start; padding: 0; margin: 0; } .widget { height: 500px; width: 300px; background-color: #E3962F; position: absolute; right: 30px; } .footer { background-color: #333; height: 800px; } 
 <body> <div class="main-content"> <div class="nav"> <h1>Sticky Sidebar Problem</h1> <i class="fa fa-arrow-down" aria-hidden="true"></i> </div> <div class="main"> <p> [Main Content] </p> </div> <div class="sidebar-container"> <p>[Sidebar Container]</p> <div class="widget"> <p> [Widget]</p> </div> </div> </div> <div class="footer"></div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> </body> 

ORIGINAL - css solution:

Depending on what your requirements are for the footer and browser compatibility, you can get the effect you are looking for without any javascript / jquery. You would have to make a minor adjustment to your html, moving your footer outside the grid and styling it separately (I just added height for the example below). Then, you could just add position: sticky and top: 10px to your .sidebar css.

Result is that sidebar starts scrolling after the header is scrolled and then stops scrolling when it bumps into the footer.

Note that position: sticky is not supported in IE (there is a polyfill for sticky ).

See the snippet below with example adjustments to your code:

 body { padding: 0; margin: 0; } .main-content { width: 100%; display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: 80px auto 400px; grid-template-areas: "nav nav nav nav" "main main main sidebar" "footer footer footer footer"; grid-column-gap: 20px; grid-row-gap: 10px; height: 100%; } .nav { grid-area: nav; background-color: #007ccc; } .main { height: 100%; max-width: 600px; justify-self: start; grid-area: main; padding: 10px; float: left; } .sidebar { height: 600px; width: 300px; justify-self: start; background-color: #4BA25E; grid-area: sidebar; grid-column: 4; margin: 10px; position: sticky; top: 10px; } .footer { background-color: #333; height: 400px; } 
 <body> <div class="main-content"> <div class="nav"></div> <div class="main"> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin tincidunt tempus nisi, vitae finibus nunc pharetra quis. Quisque in vehicula nunc. Nullam nec velit sed mi posuere ultricies pellentesque quis nibh. Morbi consequat, dui at consequat rhoncus, neque nulla posuere odio, non dapibus velit urna in turpis. In feugiat massa orci, vitae scelerisque sem posuere ut.</p> <p>Phasellus porttitor diam metus. Suspendisse quis mi sollicitudin, lobortis orci eu, pharetra ex. Cras ex nibh, dapibus quis tortor a, placerat commodo lorem. Mauris sed sapien ligula. Praesent lobortis cursus varius. Donec vulputate pulvinar interdum. Suspendisse laoreet malesuada commodo.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin tincidunt tempus nisi, vitae finibus nunc pharetra quis. Quisque in vehicula nunc. Nullam nec velit sed mi posuere ultricies pellentesque quis nibh. Morbi consequat, dui at consequat rhoncus, neque nulla posuere odio, non dapibus velit urna in turpis. In feugiat massa orci, vitae scelerisque sem posuere ut.</p> <p>Phasellus porttitor diam metus. Suspendisse quis mi sollicitudin, lobortis orci eu, pharetra ex. Cras ex nibh, dapibus quis tortor a, placerat commodo lorem. Mauris sed sapien ligula. Praesent lobortis cursus varius. Donec vulputate pulvinar interdum. Suspendisse laoreet malesuada commodo.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin tincidunt tempus nisi, vitae finibus nunc pharetra quis. Quisque in vehicula nunc. Nullam nec velit sed mi posuere ultricies pellentesque quis nibh. Morbi consequat, dui at consequat rhoncus, neque nulla posuere odio, non dapibus velit urna in turpis. In feugiat massa orci, vitae scelerisque sem posuere ut.</p> <p>Phasellus porttitor diam metus. Suspendisse quis mi sollicitudin, lobortis orci eu, pharetra ex. Cras ex nibh, dapibus quis tortor a, placerat commodo lorem. Mauris sed sapien ligula. Praesent lobortis cursus varius. Donec vulputate pulvinar interdum. Suspendisse laoreet malesuada commodo.</p> <p>In hac habitasse platea dictumst. Maecenas et mauris nunc. Aliquam erat volutpat. Duis eu condimentum ipsum. Etiam a mattis ipsum, sit amet vestibulum felis. In fermentum purus augue, vitae interdum ante gravida non. In tincidunt risus vitae ligula dignissim pellentesque. Vivamus id est eget metus placerat euismod et nec diam. Integer luctus bibendum condimentum.</p> <p>Vestibulum pretium quis mauris id pellentesque. Donec placerat iaculis ex, ut porta dolor vestibulum ac. Phasellus nunc tellus, viverra vestibulum posuere eu, faucibus vitae ex. Aenean efficitur maximus nunc, at sagittis nunc mattis sed. Vivamus et magna vitae magna interdum sodales. Ut imperdiet lobortis consectetur.</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin tincidunt tempus nisi, vitae finibus nunc pharetra quis. Quisque in vehicula nunc. Nullam nec velit sed mi posuere ultricies pellentesque quis nibh. Morbi consequat, dui at consequat rhoncus, neque nulla posuere odio, non dapibus velit urna in turpis. In feugiat massa orci, vitae scelerisque sem posuere ut.</p> <p>Phasellus porttitor diam metus. Suspendisse quis mi sollicitudin, lobortis orci eu, pharetra ex. Cras ex nibh, dapibus quis tortor a, placerat commodo lorem. Mauris sed sapien ligula. Praesent lobortis cursus varius. Donec vulputate pulvinar interdum. Suspendisse laoreet malesuada commodo.</p> <p>In hac habitasse platea dictumst. Maecenas et mauris nunc. Aliquam erat volutpat. Duis eu condimentum ipsum. Etiam a mattis ipsum, sit amet vestibulum felis. In fermentum purus augue, vitae interdum ante gravida non. In tincidunt risus vitae ligula dignissim pellentesque. Vivamus id est eget metus placerat euismod et nec diam. Integer luctus bibendum condimentum.</p> <p>Vestibulum pretium quis mauris id pellentesque. Donec placerat iaculis ex, ut porta dolor vestibulum ac. Phasellus nunc tellus, viverra vestibulum posuere eu, faucibus vitae ex. Aenean efficitur maximus nunc, at sagittis nunc mattis sed. Vivamus etmagna vitae magna interdum sodales. Ut imperdiet lobortis consectetur.</p> <p>Vestibulum pretium quis mauris id pellentesque. Donec placerat iaculis ex, ut porta dolor vestibulum ac. Phasellus nunc tellus, viverra vestibulum posuere eu, faucibus vitae ex. Aenean efficitur maximus nunc, at sagittis nunc mattis sed. Vivamus etmagna vitae magna interdum sodales. Ut imperdiet lobortis consectetur.</p> <p>Vestibulum pretium quis mauris id pellentesque. Donec placerat iaculis ex, ut porta dolor vestibulum ac. Phasellus nunc tellus, viverra vestibulum posuere eu, faucibus vitae ex. Aenean efficitur maximus nunc, at sagittis nunc mattis sed. Vivamus etmagna vitae magna interdum sodales. Ut imperdiet lobortis consectetur.</p> </div> <div class="sidebar"></div> </div> <div class="footer"></div> </body> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM