31

How can I make an element sticky, so it stays at the top of the viewport? I want the element to remain sticky even if it leaves it's container.

I tried this:

.child-sticky {
  height: 200px;
  background: #333366;
  position:sticky;
  top:20px;
  color:#ffffff;
}

.parent {
  background: #555599;
}

.child {
  background-color: #8888bb;
}

.page {
  height: 3000px;
  background: #999999;
  width: 500px;
  margin: 0 auto;
}

div {
  padding: 20px;
}
<div class="page">
  <div class="parent">
    <div class="child"><p>...<br>...<br>...<br>...<br>...</p></div>
    <div class="child-sticky">
      <p>i want to be sticky, even when I'm outside my parent.</p>
    </div>
    <div class="child"><p>...<br>...<br>...<br>...<br>...</p></div>
    <div class="child"><p>...<br>...<br>...<br>...<br>...</p></div>
  </div>
</div>
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Jules Colle
  • 11,227
  • 8
  • 60
  • 67
  • 1
    I think they know it's not ideal. If I'm interpreting [this issue](https://www.w3.org/TR/css-position-3/#issue-12e9f6da) correctly, the ideal would be that the sticky would be relative to the nearest scrolling ancestor, but complications with the CSS Object Model prevent that. – Joseph Marikle Oct 24 '17 at 14:36

6 Answers6

22

Sticky works that way, it will remain sticky relative to its parent. You need to use fixed. Check this codepen

Nicholas
  • 3,529
  • 2
  • 23
  • 31
18

Already 7 months ago, but I found a CSS only solution if the element you want to be sticky is the last one of its parent, its very simple: Just give the parent element position: sticky; and also give it top: -xx;, depending on the height of the elements before the last one.

#parent {
  position: -webkit-sticky;
  position: sticky;
  top: -3em;
}

#some_content {
  height: 3em;
}

#sticky {
  background-color: red;
}

#space {
  height: 200vh;
}
<div id="parent">
  <div id="some_content">Some Content</div>
  <div id="sticky">Sticky div</div>
</div>

<div id="space"></div>
<p>Scroll here</p>
Luka Theisen
  • 181
  • 1
  • 6
3

This is how position: sticky is intended to work. If you need it to also work outside the parent than you have to change the HTML structure.

See also the official definition: https://www.w3.org/TR/css-position-3/#sticky-pos

cloned
  • 6,346
  • 4
  • 26
  • 38
  • 1
    Nice concept as long as you can control the HTML markup. Enter WordPress and other low-code "solutions" and you have parent containers with non-semantic markup like `
    – Ingo Steinke Oct 12 '22 at 10:51
2

There is a little trick you can try. In some cases it will break your layout and in others it won't. In my case, I have a header with some buttons but when I scroll down, I want to have access to some action buttons which are inside that one-line header. The only change is to set the display property of your parent to be inline.

.parent {
  display: inline;
}
  • Very very cool, thank you, can you tell me the reason? Because I want to understand why inline solve the problem? What do you do in the father element? – eror programs Jan 01 '23 at 04:14
1

Based on https://stackoverflow.com/a/46913147/2603230 and https://stackoverflow.com/a/37797978/2603230's code, here's an combined solution that does not require hard-coded height.

$(document).ready(function() {
    // Get the current top location of the nav bar.
    var stickyNavTop = $('nav').offset().top;
  
    // Set the header's height to its current height in CSS
    // If we don't do this, the content will jump suddenly when passing through stickyNavTop.
    $('header').height($('header').height());
  
    $(window).scroll(function(){
        if ($(window).scrollTop() >= stickyNavTop) {
            $('nav').addClass('fixed-header');
        } else {
            $('nav').removeClass('fixed-header');
        }
    });
});
body { margin: 0px; padding: 0px; }

nav {
    width: 100%;
    background-color: red;
}

.fixed-header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 10;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<header>
  <div>
    <h1 style="padding-bottom: 50px; background-color: blue;">
    Hello World!
    </h1>
  </div>
  <nav>
    A nav bar here!
  </nav>
</header>

<main style="height: 1000px;">
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</main>
He Yifei 何一非
  • 2,592
  • 4
  • 38
  • 69
1

Like mentioned, sticky works that way. However there is a CSS hack that you can play around with.

Disclaimer

This is an ugly hack and will probably create a lot of problems with the following content. So I would not recommend it for most use cases. Having said that...

Negative margin hack

There is a hack you could play around with including negative margin. If you extend the height of the container and then give it some negative bottom margin, it could at least "visually leave" the container.

.child-sticky {
  height: 200px;
  background: #333366;
  position:sticky;
  top:20px;
  color:#ffffff;
}

.parent {
  height: 1250px;
  background: #555599;
  margin-bottom: -500px;
}
.following-content {
  background: red;
  height:500px;
}

.child {
  background-color: #8888bb;
}

.page {
  height: 3000px;
  background: #999999;
  width: 500px;
  margin: 0 auto;
}

div {
  padding: 20px;
}
<div class="page">
  <div class="parent">
    <div class="child"><p>...<br>...<br>...<br>...<br>...</p></div>
    <div class="child-sticky">
      <p>i want to be sticky, even when I'm outside my parent.</p>
    </div>
    <div class="child"><p>...<br>...<br>...<br>...<br>...</p></div>
    <div class="child"><p>...<br>...<br>...<br>...<br>...</p></div>
  </div>
  <div class="following-content">
  </div>
</div>
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Daniel Groner
  • 173
  • 1
  • 9