Fixed element bahaviour isn't as expected - css

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>

Related

Can a position:absolute element be made sticky?

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>

Position relative parent with absolute child

How would I go about absolutely positioning the child to the right side of the parent (with margin on all sides of the child element)? Why does the child cause the parent (with a min-height) to generate a scrollbar when the child falls outside of the normal flow of the document? What must I do to get rid of that scrollbar?
Alternatively, how could I use the calc() function in the context of a relative position of the child so I get the same outcome?
* {
box-sizing: border-box;
}
.box {
width: 50%;
min-height: 400px;
margin: 50px auto;
background: hsl(220, 80%, 50%);
overflow: auto;
position: relative;
}
.child {
width: 200px;
margin: 20px;
min-height: inherit;
background: firebrick;
position: absolute;
right: 0;
}
<div class="box">
<div class="child"></div>
</div>
The min-height: inherit; on the child inherits the value from the parent which is 400px, and the margin: 20px; makes the total height over 100%, and you have overflow: auto; sets there, it means the scrollbar will appear if the container couldn't hold the content inside.
If they are in the normal content flow, the scrollbar won't appear, since it is min-height, the container will adjust the height to fit the content. However, in the relative and absolute positions, the absolute box is taken out the normal flow, the container won't able to adjust the height to fit automatically, and that will cause the overflow when the child's height exceeds.
To get rid of the scrollbar, you can use calc() function like you mentioned. You just need to set min-height: calc(100% - 40px); on the child. Or, change overflow value to hidden on the parent, the output will be different though.
By the way, since you have box-sizing: border-box; declared, but it does not do anything for margin.

Remove mix-blend-mode from child element

How can I set mix-blend-mode on an element, but not it's children? Setting the children to the default value of normal does not seem to work:
http://jsfiddle.net/uoq916Ln/1/
The solution on how to avoid mix-blend-mode affects children:
Make child element position relative, give it a width and height;
Create some real or pseudo element inside the child with absolute position, and apply mix-blend-mode to it;
Create inner element inside the child for your content. Make it's position absolute, and put it on top of other elements;
Live example
html
<div class="bkdg">
<div class="blend">
<div class="inner">
<h1>Header</h1>
</div>
</div>
</div>
css
.blend {
position: relative; /* Make position relative */
width: 100%;
height: 100%;
}
.blend::before { /* Apply blend mode to this pseudo element */
content: '';
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 1;
background-color: green;
mix-blend-mode: multiply;
}
.inner { /* This is our content, must have absolute position */
position: absolute;
z-index: 2;
}
h1 {
color: white;
}
I know this was asked over two years ago, but it could be useful in the future as it could be a better solution than creating pseudo-elements.
There is the CSS isolation property that allows to choose wether the child element should be rendered in its parent's context (auto) or as part of a new context, thus without any blend mode applied to it (isolate).
Check out this page for examples
someone commented that the the whole block is rendered with the effect and that is why you're having the issue. I am able to accomplish what you're are trying to do by removing the h1 from the block, position absolute, and a z-index of 1. here is a jsfiddle to show the effect.
html
<div class="bkdg">
<h1>Header</h1>
<div class="blend">
</div>
</div>
css
.blend {
background-color: green;
mix-blend-mode: multiply;
width: 700px;
height: 35px;
}
h1 {
color: white;
position: absolute;
top: -15px; left: 10px;
z-index: 1;
}
https://jsfiddle.net/jckot1pu/
It’s impossible to remove an element’s mix-blend-mode from its children.
MDN says that mix-blend-mode:
sets how an element's content should blend with the content of the element's parent and the element's background
To achieve the desired effect, place the child in a separate stacking context and make sure it renders on top of the element with mix-blend-mode set.
You need two things to make this work:
Make sure that your opaque content (your text) is not a child of the element that sets the background and the blend mode. For example, with CSS Grid Layout.
Make sure the text is rendered over, and thus not affected by, the element that sets the background and the blend mode. Setting mix-blend-mode on your background will create a stacking context for it, and you may need to give your content its own stacking context to ensure it gets rendered above it.
Position your elements with CSS Grid:
define a grid container with one auto-sized grid area
place both the background element and the text element into that one grid area (so that they overlap)
let the text element dictate the size of the grid area
have the background element stretch to the size of the grid area, which is dictated by the size of the text element
Then, set isolation: isolate on the text element to ensure it gets rendered above, and not under the background element.
A working example
.container {
display: grid;
grid-template-areas: 'item';
place-content: end stretch;
height: 200px;
width: 400px;
background-image: url(https://picsum.photos/id/237/400/200);
background-size: cover;
background-repeat: no-repeat;
}
.container::before {
content: '';
grid-area: item;
background-color: seagreen;
mix-blend-mode: multiply;
}
.item {
grid-area: item;
isolation: isolate;
color: white;
}
h1,
p {
margin: 0;
padding: 10px;
}
<div class="container">
<div class="item">
<h1>HEADLINE</h1>
<p>Subhead</p>
</div>
</div>
An important note if you're using the excellent pseudoelement ::before/::after solution posted by Rashad Ibrahimov.
I found that I had to remove z-index from the parent element and apply it only to the pseudoelements and child elements before mix-blend-mode: multiply would work.
For example
#wrapper {
position: relative;
}
#wrapper .hoverlabel {
position: absolute;
bottom: 0;
left: 0;
right: 0;
/* z-index: 90; Uncomment this to break mix-blend-mode. Tested in Firefox 75 and Chrome 81. */
}
#wrapper .hoverlabel::before {
position: absolute;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
mix-blend-mode: multiply;
z-index: 90;
background-color: rgba(147, 213, 0, 0.95);
}

Overflow-x: hidden also hides vertical content too

I have a DIV measuring 400px wide, containing two DIVs side-by-side, each with width of 400px and height of 600px. The width of both DIVs is fixed, however the height can vary. I'd like to hide the second DIV and show the first completely, with no scrolling inside the DIV.
My solution, I thought, was to hide the overflow-x. This seems to also hide the y overflow too.
Here's my code:
#schools-sub-nav {
}
#schools-container {
width: 400px; /* Set the width of the visible portion of content here */
background-color: fuchsia;
position: relative;
overflow-x: hidden;
}
#schools-list {
width: 400px; /* Set the width of the visible portion of content here */
height: 600px; /* Delete the height, let the content define the height */
background-color: purple;
position: absolute;
top: 0;
left: 0;
}
#boards-list {
width: 400px; /* Set the width of the visible portion of content here */
height: 600px; /* Delete the height, let the content define the height */
background-color: green;
position: absolute;
top: 0;
left: 400px;
}
<div id="schools-sub-nav"> Schools List // Boards List </div>
<div id="schools-container">
<div id="schools-list"> One </div>
<div id="boards-list"> Two </div>
</div>
I expect #schools-list to be visible, but for some reason overflow-x: hidden in #schools-container hides it.
The way you made the two divs (with an absolute position) void the overflow rule!
You need to change the position type (to normal/not absolute) and I suggest using floats, finally, the container div that you want to apply the overflow, needs to have a way to fit it, like placing a div at the end with clear: both (in the case of using floats).
EDIT: I just tried it and you can hide the second div by following the upper suggestion and adding another surrounding div inside with a very large width and change the overflow-x to overflow for the main container div.
Like this:
<div id="schools-container">
<div id="schools-container-inside">
<div id="schools-list"> One </div>
<div id="boards-list"> Two </div>
</div>
</div>
And then the CSS (I commented the original not used CSS and added the new div class at the end):
#schools-container {
width: 400px; /* Set the width of the visible portion of content here */
background-color: fuchsia;
position: relative;
/*overflow-x: hidden;*/
overflow: hidden;
}
#schools-list {
width: 400px; /* Set the width of the visible portion of content here */
height: 600px; /* Delete the height, let the content define the height */
background-color: purple;
/*
position: absolute;
top: 0;
left: 0;
*/
float: left;
}
#boards-list {
width: 400px; /* Set the width of the visible portion of content here */
height: 600px; /* Delete the height, let the content define the height */
background-color: green;
/*
position: absolute;
top: 0;
left: 400px;
*/
float: left;
}
#schools-container-inside {
width: 10000px;
overflow: hidden;
}
JsFiddle here: http://jsfiddle.net/MbMAc/
I think you need this
#schools-container {
width: 400px; /* Set the width of the visible portion of content here */
background-color: fuchsia;
position: relative;
overflow-x: hidden;
overflow-y:auto;
height:600px;
}
You need to define height of main div as well.

child div not expanding to parent div

So I have three div's
One parent and two child.
The parent is as follows:
#parent {
overflow:auto;
margin: 0 auto;
margin-top:37px;
min-height: 100%;
width:875px;
}
the two child divs are as follows
#child1 {
overflow:auto;
min-height:150px;
border-bottom:1px solid #bbb;
background-color:#eee;
opacity:0.4;
}
#child2 {
height:100%;
background-color:white;
}
The parent div extends 100% as I can see the borders of it till the end of the page but the child2 is not extending down to the end of the page like the parent div.
height doesn't behave the way you seem to be anticipating. When you specify height: 100% that percentage is calculated by looking up the DOM for the first parent of said element with a height specified that has absolute or relative positioning.
You can cheat when it comes to the body tag, so if you had something like this:
<body>
<div style="height: 100%">
</div>
</body>
Some browsers/versions will behave the way you expect by taking up the total height of the page. But it won't work when you go any deeper than that.
Here is the approach I use to strech a div to the bottom of the page, it involves absolute positioning (nice thing about this one is that it is pretty cross-browser compliant and doesn't require javascript to pull it off):
<div id="parent">
<div id="childNorm"></div>
<div id="childStrech"></div>
</div>
#parent
{
position: absolute;
width: 1000px;
bottom: 0;
top: 0;
margin: auto;
background-color: black;
}
#childNorm
{
position: absolute;
width: 1000px;
top: 0;
height: 50px;
background-color: blue;
color: white;
}
#childStrech
{
position: absolute;
width: 1000px;
top: 50px;
bottom: 0;
background-color: red;
color: white;
}
Here is a Jsfiddle for demo: http://jsfiddle.net/t7ZpX/
The trick:
When you specify absolute positioning and then put in bottom: 0; that causes the element to stretch to the bottom of the page; You just have to worry about positioning the elements as a trade off.
Yes, this is one of the annoying things in css. min-height is not considered a "height" for purposes of calculating height. See http://jsfiddle.net/3raLu/3/. You need to have height: 100% on the parent div to make the child full height. Or, if you can have it be absolutely positioned, then this works: http://jsfiddle.net/3raLu/6/.

Resources