7

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>
jarrodwhitley
  • 826
  • 10
  • 29

2 Answers2

1

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).

nmg49
  • 1,356
  • 1
  • 11
  • 28
1

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() {
    const windowTop = $(this).scrollTop();
    const 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;
}
<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>

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>
benvc
  • 14,448
  • 4
  • 33
  • 54
  • This actually won't work for me. Turns out I'll need a jQuery solution. – jarrodwhitley Aug 31 '18 at 16:35
  • In your fiddle, you can add `position: sticky` and `top: 0` to your `.widget` css, comment out all the javascript, and then the widget will scroll between the bottom of the `p` element and the bottom of `.sidebar-container`. Are you wanting the widget to scroll over the top of the `p` element and/or beyond the bottom of `.sidebar-container` (until it gets to the footer)? – benvc Aug 31 '18 at 17:58
  • 1
    Yes, the widget must remain in the `.sidebar-container` but be able to scroll out of it. The height of that container cannot be changed. That is why `position: sticky` will not work because it keeps the widget in side the container. Only fixed position will allow it to scroll all the way down. I do not believe there is a CSS solution for this problem. – jarrodwhitley Aug 31 '18 at 18:03
  • Also, is there a reason you didn't give my post a vote? Is it unhelpful or unclear? I currently have a question ban because people answer my posts but don't vote. So I can't really do anything about it. – jarrodwhitley Aug 31 '18 at 18:05
  • @benvc Your code is valid, but the OP specifically asked for a *JS* solution. Therefore, the fact that this can be done in CSS is kinda besides the point. – nmg49 Aug 31 '18 at 18:16
  • 1
    @JarrodWhitley Your grammar is quite a bit off and it's difficult to understand your question. Plus as I remember it, you took out quite a bit of context so somebody new coming to this question will have a difficult time understanding what you mean. Please edit this question, add some more context, and fix the grammatical issues (to the best of your ability), and post another comment here. Then I will upvote your question. Also, you may want to have a look at SO's page of how to ask a good question: https://stackoverflow.com/help/how-to-ask. – nmg49 Aug 31 '18 at 18:20
  • @nmg49 absolutely clear that OP asked for a js solution, my answer does not provide that js solution for various reasons and should be treated accordingly. – benvc Aug 31 '18 at 18:30
  • @benvc I agree with you that my code should be in a snippet. I'll make it so. – jarrodwhitley Aug 31 '18 at 18:31
  • @benvc yeah, I'm learning that lesson the hard way. I deleted a bunch of noob questions that could have been answered with a google search from a few years ago. Apparently that's a no-no :( – jarrodwhitley Aug 31 '18 at 18:39
  • Well, I reworded almost everything and moved my code from jsfiddle into a snippet. I hope that makes it better. – jarrodwhitley Aug 31 '18 at 18:41
  • The snippet works perfectly, but I'm getting a lot of console errors on my site (detailed above) – jarrodwhitley Sep 04 '18 at 18:54
  • @JarrodWhitely The error you are seeing in the console says that you have a variable `s` somewhere that has not been defined. There is no such reference in the code snippet here so it is probably coming from some other script, plugin, etc on your site. If you hunt down the source of the undefined reference and can't fix it, feel free to post another question for help but not much we can do without the relevant code to look at. – benvc Sep 04 '18 at 21:39
  • @benvc that makes sense. Thanks – jarrodwhitley Sep 05 '18 at 14:16
  • I'm getting the error >VM2559:11 Uncaught TypeError: Cannot read property 'top' of undefined on this line `const endTop = footer.offset().top - widget.height() - space;` – jarrodwhitley Sep 07 '18 at 18:56
  • Does your footer have the "footer" class assigned? If not you will need to update your "footer" variavle assignment with the class or id for your footer html. – benvc Sep 07 '18 at 19:05