Can a position:absolute element be made sticky? - css

In CSS, position: sticky enables an element to display with a position: static behaviour (ie. it adopts its default position within the document flow) until it reaches a certain scroll position, after which it adopts position: fixed behaviour.
So... does that mean we cannot use position: sticky on an element which requires a normal behaviour of position: absolute?
Context:
I have an out-of-flow element which occupies a position towards the top-left corner of the viewport. After an inch or two of scrolling, the element hits the top of the viewport and, ideally, I'd like it not to carry on disappearing at that point.

You actually can leverage display: grid and have a sticky element that doesn't pushes its siblings:
header {
display: flex;
align-items: center;
justify-content: center;
height: 50vh;
border: 1px dashed #f00;
}
main {
display: grid;
}
div {
display: flex;
align-items: center;
justify-content: center;
}
.section {
grid-column: 1;
height: 100vh;
border: 1px dashed #0f0;
}
.first.section {
grid-row: 1;
}
.sticky {
grid-row: 1;
grid-column: 1;
position: sticky;
top: 0;
height: 30vh;
border: 1px dashed #0ff;
}
footer {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
border: 1px dashed #f00;
}
<header>I'm the header</header>
<main>
<div class="sticky">I'm sticky</div>
<div class="first section">Just</div>
<div class="section">some</div>
<div class="section">sections</div>
</main>
<footer>I'm the footer</footer>
The trick here is to place the sticky section and its first sibling on the first row and first column of their parent (because grids allow us to place many elements in the same cell).
The sticky element remains sticky in its parent so it will stay on scroll beyond its cell.

As GibboK says, the default positioning scheme isn't absolute positioning, it's the static position. Elements are laid out in normal flow by default — if out-of-flow were the default, then the default HTML page would be impossible to read. Besides, absolutely positioned elements do scroll with the page most of the time — the only time you can make an absolutely positioned behave like a fixed positioned element with respect to page scrolling is through some semi-complicated CSS.
If you're asking whether it's possible for
a stickily positioned element to be out-of-flow when stuck and unstuck, or
for the containing block of a stickily positioned element to be determined the same way as for an absolutely positioned element,
then unfortunately neither of these is supported by sticky positioning.

The point of position:sticky is that it is only fixed while the parent element is not in view. A position:absolute element isn't attached to it's parent.
It could be interesting if such a position would exist and the rule would be that the element would be absolute, while the element it is absolute positioned to is in view, but currently there exists nothing like this nativley, but you could try to recreate it using JS.

A way to make a sticky element look like it's absolutely positioned
I came up with this hack that achieves the goal, but I haven't figured out how to fix its one flaw: There's a blank area at the bottom of the scrollable content equal to the height of the sticky element + its initial vertical offset.
See the comments in the code for an explanation of how it works.
#body {
width: 100%;
position: relative;
background: Linen;
font-family: sans-serif;
font-size: 40px;
}
/* to position your sticky element vertically, use the
height of this empty/invisible block element */
#sticky-y-offset {
z-index: 0;
height: 100px;
}
/* to position your sticky element horizontally, use the
width of this empty/invisible inline-block element */
#sticky-x-offset {
z-index: 0;
width: 100px;
display: inline-block;
}
/* this element is sticky so must have a static position,
but we can fake an absolute position relative to the
upper left of its container by resizing the invisible
blocks above and to the left of it. */
#sticky-item {
width: 150px;
height: 100px;
border-radius: 10px;
background-color: rgba(0, 0, 255, 0.3);
display: inline-block;
position: sticky;
top: -80px;
bottom: -80px;
}
/* this div will contain the non-sticky main content of
the container. We translate it vertically upward by
sticky-y-offset's height + sticky-item's height */
#not-sticky {
width: 100%;
background-color: rgba(0, 0, 255, 0.1);
transform: translateY(-200px);
}
.in-flow {
width: 90%;
height: 150px;
border-radius: 10px;
margin: 10px auto;
padding: 10px 10px;
background: green;
opacity: 30%;
}
<div id="body">
<div id="sticky-y-offset"></div>
<div id="sticky-x-offset"></div>
<div id="sticky-item">absolute & sticky</div>
<div id="not-sticky">
<div class="in-flow">in flow</div>
<div class="in-flow">in flow</div>
<div class="in-flow">in flow</div>
<div class="in-flow">in flow</div>
</div>
</div>

Related

Fixed element bahaviour isn't as expected

I have a case where the fixed element is 'positioned' - in a way - relative to the parent container and not the browser window...
.container {
position: relative;
width: 640px;
margin: 0 auto;
}
.options {
position: fixed;
bottom: 0;
width: 100%;
}
.options button {
float: left;
box-sizing: border-box;
width: 25%;
}
<div class="container">
<div class="options">
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
</div>
</div>
The div containing the buttons is fixed, and the bottom offset is set to 0. However, the div is offset a certain distance, and starts at the same horizontal distance as the parent container. Now once I set the left offset value to '0' the element positions itself as expected, starting from the edge of the viewport. So I understand that this isn't actually positioned in relation to the parent container. But why is there an offset initially? I'm guessing the default 'auto' setting computes that left value to something other than zero. But how is this value computed?
Also, another point of confusion arose with how the width value is computed, when the value is 100% vs inherit. I looked up and realized that the difference between the two is that while 'width: 100%' sets the width to 100% of the computed value of the parent element, 'width: inherit' takes up the CSS value of the parent literally and applies that to the element. So in the case of the sample I shared above, while the former will set the width of the div containing the buttons to literally 100% of its own parent's computed width, the latter will set to width to '640px', which is the parent's CSS value. The issue I have is, in the first case, where the width is set in percentages, the element is wider than expected. The computed width appears to be computed from the viewport width i.e. 100% of the viewport, and not the parent element, which is what I expected.
If 'width: inherit' acquires the width of 640px from the parent element, then why is the width inherited from the viewport if width is set to 100%, and not the computed value of the parent. Why is it that in one case, the preceding container is considered as the parent from which the value is inherited, and in the other case the viewport is the parent from which the value is computed?
But why is there an offset initially?
That's the case with position values other than static and sticky (fixed, absolute) they keep their parent offsets.
Demo:
*,
*:after,
*:before {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body * {
padding: 10px;
border: 1px solid;
}
body {
text-align: center;
}
[container] {
position: relative;
width: 120px;
margin: 50px;
padding: 30px;
display: inline-block;
}
[absolute] {
position: absolute;
}
[fixed] {
position: fixed;
}
<div container>
<div absolute>absolute</div>
</div>
<br/>
<div container>
<div fixed>fixed</div>
</div>
As you can see even with position applied, the elements don't suddenly jump around to where they're expected to be. You can try adding left:0 add see how they move.
Width: inherit or 100%; ?
Like i said before:
percentage is relative to the containing block, that changes according to css rules, inherit keyword is relative to the parent element defined in the markup and that never changes with css
Much more accurate MDN explanation
Percentage values that are applied to the width, height, padding, margin, and offset properties of an absolutely positioned element (i.e., which has its position set to absolute or fixed) are computed from the element's containing block.
Demo
[container] {
background: orange;
position: relative;
width: 300px;
height: 100px;
transform: translate(0)
}
[fixed] {
background: red;
position: fixed;
width: 100%;
left: 0;
}
<div container>
<div fixed>fixed</div>
</div>
In this example width:100% is behaving as it should because we changed the congaing block of the fixed element using transform: translate(0)
There's many ways to change the containing block of an element i suggest you read up the MDN article on that.
body have a padding per default. You can add padding: 0 to make your fixed element go as far to the left as possible, or add left: 0 to your fixed .options element.
You shouldn't use fixed width (640px), because you should code with responsiveness in mind.
You shouldn't use float in layout, because it mess up the box size of the object. Only use it on images if you want the text to "float" around the image.
html, body { /* ADDED */
margin: 0;
padding: 0;
}
.container {
position: relative;
width: 100%; /* CHANGED */
margin: 0 auto;
/* ADDED */
min-width: 640px;
}
.options {
position: fixed;
bottom: 0;
width: 100%;
/*left: 0; if you don't want to change the padding for the body */
/* ADDED */
display: flex;
}
.options button {
/*float: left;*/
box-sizing: border-box;
/* width: 25%; */
/* ADDED */
flex: 1 1 auto; /* grow / shrink / auto-adjust in width */
}
<div class="container">
<div class="options">
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
</div>
</div>

How do I create a horizontal line of divs, some of which flow to the right? All divs should be vertically aligned

Perhaps it will be easiest to show what I'd like to achieve on an example:
header {
position: absolute;
top: 0;
left: 0;
width: 100%;
background-color: lightgray;
border-bottom: 5px solid gray;
}
header > div {
display: inline-block;
height: 100%;
vertical-align: middle;
padding: 10px;
}
<header>
<div><h1>Title</h1></div>
<div>Blah1</div>
<div>Blah2</div>
<div style="float: right;">Blah4</div>
<div style="float: right;">Blah3</div>
</header>
I hope the problem is clear... I want my divs to line up horizontally in the header. To achieve this, I reason, I should make these divs inline-block. And this works.
Except one thing... I want some of these divs to float to the right of the page, rather than to the left. But then, no matter what I do, these divs refuse to vertically align themsleves to the middle of the enclosing container. As you can see, Blah3 and Blah4 are far too much to the top, which looks ugly.
I was reasoning that if I use height: 100% I'll force the rightmost divs to be as tall as the encloding header, and then, if I use vertical-align: middle I'll vertically position the text in the, well, middle; but as you can see this is not the case.
How to fix this?
I'd rather suggest you the Flexbox solution:
header {
display: flex; /* displays flex-items (children) inline */
align-items: center; /* centers them vertically */
position: absolute;
top: 0;
left: 0;
width: 100%;
background: lightgray;
border-bottom: 5px solid gray;
}
header > div {padding: 10px}
header > div:nth-child(3) {margin-right: auto} /* pushes the other two siblings far right to the end of the row */
<header>
<div><h1>Title</h1></div>
<div>Blah1</div>
<div>Blah2</div>
<div>Blah4</div>
<div>Blah3</div>
</header>

position:sticky failing down the page

Hoping someone can help with this. Have a menu bar that is set to position:sticky. It starts at 50px from the top on page load. When the document is scrolled, the menu bar sticks at the top of the page, as expected, until it "hits" another element further down the page, at which point it's scrolled above the top of the viewport, along with everything else.
Originally, I thought it was "running" into flex item(s) or something that had position:relative. That's not the case.
Has anyone come across this? I'd provide a code sample, but I'm not entirely sure what is causing the issue.
MDN: Sticky positioning can be thought of as a hybrid of relative and fixed positioning. A stickily positioned element is treated as relatively positioned until it crosses a specified threshold, at which point it is treated as fixed until it reaches the boundary of its parent.
Note that sticky, by specification, will not work inside element with overflow: hidden or auto
Use this link: https://developer.mozilla.org/en-US/docs/Web/CSS/position
Example:
The sticky button always moves till its parent div's edges.
section {
height: 200vh;
display: flex;
}
section nav {
background: wheat;
width: 30vw;
display: flex;
}
section nav button.btn {
background: #9b59b6;
border: 0px;
color: white;
flex: 1;
max-height: 50px;
padding: 1rem;
position: sticky;
top: 0px;
}
section div {
width: 100%;
background: pink;
}
header, footer {
text-align:center;
background: #2c3e50;
color:white;
}
header{
padding:1rem;
}
footer{
height:100vh;
}
<header>Scroll the page to see the sticky effect.</header>
<section>
<nav>
<button class="btn">Sticky Button</button>
</nav>
<div>
<article>Hello!</article>
</div>
</section>
<footer>Footer</footer>

CSS Div Position Behaviour

I have issues with understanding the div position (relative, absolute, fixed) properties. I basically have an absolute div centered. Inside the div it should be possible to scroll vertically and horizontally. Inside this div should be a fixed header with a width larger than to screen (overflow) and a content div which has an overflow vertically and horizontally as well.
html,
body {
height: 100%;
width: 100%;
background: #fff;
margin: 0px auto;
padding: 0px auto;
position: fixed;
}
.container {
width: calc(100% - 20px);
height: calc(100% - 20px);
top: 10px;
left: 10px;
background: #2924aa;
overflow: scroll;
display: flex;
position: absolute;
z-index: 20;
}
.container-header {
width: calc(100%);
height: calc(10%);
background: #2924aa;
overflow: visible;
z-index: 10;
position: fixed;
background: red;
}
.container-body {
width: calc(110%);
height: calc(110%);
background: #2924aa;
overflow: auto;
position: absolute;
background: green;
}
<div class="container">
<div class="container-header"></div>
<div class="container-body"></div>
</div>
Here is my plunker:
https://plnkr.co/edit/wCWvHPcuYmVMql5HulHy
So i think the main question you have is in regards to the Position Attribute in CSS3. Below is a brief summary of each possible value.
CSS Positioning
The CSS positioning attribute of position has four different values.
Static - Static is the default value for position. It keeps the element on the page in its place, and it scrolls up the page as you scroll.
Relative - Relative positioning is pretty much as the same as static; however, you can use the left, right, top, and bottom attributes to alter the placement of the element relative to its original position.
Fixed - A fixed element's position is in relation to the viewport (i.e. the browser) therefore, an element with a fixed position does not scroll with the page, because when you scroll the viewport does not change. However, if you resize the browser, the element will change position.
Absolute - A element with an absolute position, is positioned relative to its parent element (i.e. the element that contains it).
A good resource for more information, including some diagrams can be found here.

Why does "flex-wrap: wrap" break "align-items"?

(I'm using Chrome v.39+)
I'm trying to use the flex-wrap property to stack child divs both horizontally and vertically, but I'm seeing some very strange behaviors. For example, if there's 3 child divs and the last is given a width of 100% (causing it to wrap) there will be unwanted gaps introduced.
Sometimes I can force the first 2 divs to honor align-items: stretch by giving them height: 100% or height: calc(100% - 1px), other times they won't stretch passed the mysterious gap, and sometimes they'll even disappear all together if I try to force them to stretch.
Here's a simplified example of the problem. They grey shouldn't be visible.
Why are these gaps appearing in flex-wrapped divs and how can I prevent them?
The gray area is still visible at the bottom because you set a height on the parent container.
If you don't want to see that gray area, remove the height from the container and add a fixed height that you require on one of the elements in the first row
DEMO
.a {
width: 300px;
display: flex;
flex-wrap: wrap;
position: relative;
top: 100px;
left: 200px;
background-color: #999;
}
.b {
height: 150px;
background-color: #00ff00;
}
.c {
background-color: #0000ff;
}
.d {
background-color: #ff0000;
}
.b {
flex-grow: 1;
flex-shrink: 0;
}
.c {
width: 5px;
flex-shrink: 0;
}
.d {
width: 100%;
height: 10px;
flex-shrink: 0;
}
<div class='a'>
<div class='b'></div>
<div class='c'></div>
<div class='d'></div>
</div>
Note: If you want to avoid fixed dimensions - just remove the height:10px from the red div.
This will ensure that there are no gaps and that each row has equal height
DEMO

Resources