Position sticky not working for horizontal scrolling when element width increases - html

I am trying to keep an element from scrolling past left: 0 using position: sticky. This works fine in some cases, but I have noticed that if the element width increases it stops working. For example, the following works:
#header {
position: sticky;
left: 0;
width: 50%;
background-color: #888;
}
#page {
height: 80vh;
width: 120vw;
background-color: #000;
}
<div>
<div id="header">
Where is my mind?
</div>
<div id="page">
</div>
</div>
But if I increase the witdth of header element to 100% it stops working.
#header {
position: sticky;
left: 0;
width: 100%;
background-color: #888;
}
#page {
height: 80vh;
width: 120vw;
background-color: #000;
}
<div>
<div id="header">
Where is my mind?
</div>
<div id="page">
</div>
</div>
Why does this happen? And is there any way to use position: sticky to prevent the header element from scrolling when it's width is 100%? I prefer not to use position: fixed in this case.

I now understand what is happening. The issue is the different way the browser treats the width and height of a <div>. The default values of auto mean that the width of the <div> is 100% while the height is set by the content. If the content is wider than 100%, then on horizontal scroll the sticky element hits the end of the container <div> and, since it cannot leave the confines of the container, begins to scroll. This doesn't happen in the same situation for vertical scrolling since the container <div> is as tall as the content by default.
To prevent this happening, we have to ensure that the container <div> is as wide as its content. This can be done in most browsers (not Edge or Explorer) by including width: max-content in the container style. Alternatively, as proposed in mfluehr's answer, putting overflow: auto creates a new block formatting context that is as wide as the content. Another option is to use display: inline-block or inline-flex etc. to cause the container <div> to base its width on the content.
For example, using two of these techniques, you can create headers, sidebars and footers that stick for a page that can scroll vertically and horizontally:
body {
padding: 0;
margin: 0;
}
#app {
overflow: auto;
height: 100vh;
}
#header {
background: blue;
width: 100%;
height: 40px;
position: sticky;
top: 0;
left: 0;
z-index: 10;
color: white;
}
#sidebar {
position: sticky;
background: green;
width: 200px;
height: calc(100vh - 40px);
top: 40px;
left: 0;
color: white;
flex-grow: 0;
flex-shrink: 0;
}
#container {
display: inline-flex;
}
#content {
background: #555;
height: 200vh;
width: 200vw;
background: linear-gradient(135deg, #cc2, #a37);
flex-grow: 0;
flex-shrink: 0;
}
#footer {
background: #000;
height: 100px;
z-index: 100;
left: 0;
position: sticky;
color: white;
}
<div id="app">
<div id="header" ref="header">
Header content
</div>
<div id="container">
<div id="sidebar" ref="sidebar">
Sidebar content
</div>
<div id="content" ref="content">
Page content
</div>
</div>
<div id="footer" ref="footer">
Footer content
</div>
</div>

This is an interesting problem. I don't know why, but putting overflow: auto on the container around the <div>s seems to fix the issue.
You can add height: 100vh to the container to let the content inside overflow with scrollbars.
body {
margin: 0;
}
#container {
overflow: auto;
height: 100vh;
}
#header {
position: sticky;
left: 0;
width: 100%;
background-color: #888;
}
#page {
height: 200vh;
width: 120vw;
background: linear-gradient(135deg, #cc2, #a37);
}
<body>
<div id="container">
<div id="header">
This is the header.
</div>
<div id="page">
Page content goes here.
</div>
</div>
</body>

Related

Div leak parent container max width

I need to make a child div leak the parent container max width.
Now, i can only leak parent padding (knowing it).
I need this to wrap all the page on a container and make some sections leak.
Without this, i will need to set the container on every section.
Here is some snippets
Better snippet on codepen
.container {
max-width: 500px;
margin: 0 auto;
padding: 0 30px;
background: lightblue;
}
.child {
background: lightcoral;
height: 200px;
}
.child.cancel-padding {
margin: 0 -30px;
}
.child.leaked {
}
html,
body {
margin: 0;
padding: 0;
}
* {
text-align: center;
font-family: sans-serif;
}
<small>note: see in fullscreen or on codepen</small>
<h1>what i have</h1>
<div class="container">
<div class="child">A element inside a container</div>
</div>
<h1>what i need</h1>
<div class="container-leaked">
<div class="child leaked">
a element inside the container, but leaking all view width (100vw)
</div>
</div>
<h1>what i can do now</h1>
<div class="container">
<div class="child cancel-padding">Make the element cancel the parent padding, that's all</div>
</div>
<h1>Why i need</h1>
<p>
i will wrap all the page in the container, but sometimes i need sections to leak the container with full view width.
</p>
Note: on the demo, i've set the child height, but i will not have control of it. it's a dynamic content div, so height is dynamic.
You can do it by using relative positioning. Indeed, you need position: relative on your container and your child -leak. Then, to center your child, you use
left: 50%;
transform: translateX(-50%);
This works because your container is centered. So left: 50% will move the child left edge to 50% of its parent width from its initial position (which mean the center of its parent). Then, transform: translateX(-50%) will move the left edge of your child 50% of its width on the left. You then just need to add width: 100vw to make your child full width. Here is the snippet:
.page {
position: relative;
}
.container {
max-width: 100px;
margin: 0 auto;
padding: 0 30px;
background: lightblue;
position: relative;
}
.child-leak {
height: 200px;
background: lightcoral;
position: relative;
left: 50%;
transform: translateX(-50%);
padding: 10px;
width: 100vw;
}
html, body{
margin: 0;
padding: 0;
text-align: center;
font-family: sans-serif;
}
<div class="page">
<h1>My title</h1>
<div class="container">
<div class="child-leak">
My full width child
</div>
</div>
<div>Below content</div>
</div>
This technique for horizontally center an element works also for vertical centering. This works because a value in % for top, left, right and bottom refers to the first non static parent width and height. On the other hand, translate with a value in % use the element width and height.
This may be a little bit of a hack, but I've done it before by adding ::before and ::after. Add position: relative to the .child and then add the following css
.child.leaked:before{
content:"";
display: block;
position: absolute;
height: 100%;
width: 100%;
left: -100%;
background-color: red;
top: 0;
}
.child.leaked:after{
content:"";
display: block;
position: absolute;
height: 100%;
width: 100%;
right: -100%;
background-color: red;
top: 0;
}
Here is an approach you can try:
.container {
max-width: 500px;
margin: 0 auto;
padding: 0 30px;
background: lightblue;
}
.child {
background: lightcoral;
height: 200px;
width: 400%;
margin-left: -150%; /* (width of child - 100) / 2 */
}
html,
body {
margin: 0;
padding: 0;
overflow: hidden; /* prevent horizontal scroll bar */
}
* {
text-align: center;
font-family: sans-serif;
}
<div class="container">
<div class="child">I am outside the parent</div>
</div>

inline divs for horizontal scroll UI, second div is not at top

I have the following setup which results in a horizontal scrolling UI with different "pages". Each page is the full width of the device/browser. Everything seems okay, except that #other is not at the top of the screen. It looks more like this (if you are mid-scroll between the two pages):
Using Chrome's dev tools I have confirmed that the height of the element is correct. It's actually behind the footer and about 400 pixels "lower" than the left div.
HTML
<div id="menu-header"></div>
<div id="pageContainerContainer">
<div id="pageContainer">
<div id="home" class="page">Stuff here</div>
<div id="other" class="page">Other things</div>
</div>
</div>
<div id="menu-footer"></div>
CSS
#menu-header, #menu-footer {
position: absolute;
left:0;right:0;
height: 80px;
z-index:50;
}
#menu-header { top: 0; }
#menu-footer { bottom: 0; }
#pageContainerContainer {
position: absolute;
top: 80px;
bottom: 80px;
width: 100vw;
overflow-x: auto;
overflow-y: hidden;
}
#pageContainer {
height: 100%;
width: 200vw;
overflow-x: auto;
overflow-y: hidden;
}
.page {
display: inline-block;
box-sizing: border-box;
width: 100vw;
height: 100%;
}
Adding position: relative to the .page class fixed the issue.

Div with position absolute having innerwidth of div with scrollbar

The last two days I've been reading most questions here and a lot more about 'fill remaining width' and 'escaping overflow: hidden', but I can't get my problem solved. At the moment, I seriously doubt if it is possible at all.
I have a scrolling box with full body width. On top of that I have a absolute positioned header that I need to make the exact same width as the scrollbox. My intention is to make the header 0px or (if needed) 1px in height and let the content overflow.
Here is a fiddle.
The scrollbox has a scrollbar (always visible), the header obviously not. To compensate for that, I float a fake scrollbar to the right inside the header container, and left of that a <div> filling the remaining width (being exactly the innerwidth of the scrollbox).
HTML
//THE SCROLLBOX
<div id="scrollbox">
<div id="center2">
content<br>content<br>...
</div>
</div>
// THE HEADER
<div id="header_box">
<!--- FAKE SCROLLBAR -->
<div id="scroller">
<div></div>
</div>
// REMAINING WIDTH
<div id="container">
<div id="FIRST">
<div id="FIRST_banner"></div>
</div>
</div>
<div id="SECOND">
<div id="SECOND_banner"></div>
</div>
</div>
</div>
CSS
#header_box {
background: yellow;
position: absolute;
left: 0;
top: 0;
right: 0;
height: 25px;
width: 100%;
overflow: visible;
}
#scroller {
float: right;
overflow-x: hidden;
overflow-y: scroll;
height: 50px;
width: auto;
/* visibility: hidden; */
}
#scroller>div {
width: 0px;
height: 101%;
}
#container {
display: inline;
width: auto;
height: 50px;
overflow: visible;
}
#FIRST {
position: relative;
overflow: hidden;
height: 25px;
background: pink;
}
#FIRST_banner {
position: absolute;
top: 0;
left: 50%;
height: 220px;
width: 30px;
background: crimson;
}
#SECOND {
background: darkcyan;
position: relative;
height: 5px;
}
#SECOND_banner {
position: absolute;
top: 0;
left: 50%;
height: 220px;
width: 30px;
background: blue;
}
The problem lies in the div (#FIRST) with remaining width. From all the solutions I've read only the one with
position: relative;
overflow: hidden;
works for me. It gives the exact width, lining up the center of the header and the scrollbox nicely. But I can't break out of the overflow: hidden, so it cuts off the content.
So my second thought was: wrap #FIRST in a #container and let the child determine the width of the parent. After that, I can put another div (#SECOND) inside the container with the width of the parent. It works partially. The #container has the width intended, and the #SECOND div overflows nicely but takes on the width of #header_box, as no width is set on the parent itself.
So, my questions:
Can I somehow break out of the overflow: hidden of the FIRST div? (In that case the container and second div can be removed).
Is there a way to let the SECOND div obey the width of it's parent.
Some totally different solution.
Sadly there is a catch to this all:
css only
no javascript
no flexbox
Thanks voor any toughts.
In the end, it was the good old <table> that saved the day, much simpler than I tought. There still is a fake scrollbar, but the absolute header now aligns perfect with the contents of the scrollable div behind it, and it remains fluid.
See fiddle here
HTML:
<!--- HEADER -->
<div id="THIRD">
<div id="THIRD_A">
<div id="THIRD_B"></div>
<div id="THIRD_C"></div>
<div id="THIRD_D"></div>
</div>
</div>
<!--- FAKE SCROLLBAR -->
<div id="scroller">
<div></div>
</div>
</div>
CSS:
/* The container for the header */
#header_box{
position: absolute;
left: 0;
top: 0;
right: 0;
height: 0px;
width: 100%;
overflow: visible;
display: table;
}
/* Takes on the width of its child: the fake scrollbar */
#scroller {
display: table-cell;
float: right;
overflow-x: hidden;
overflow-y: scroll;
height: 0px;
width: auto;
}
/* This triggers a scrollbar to be shown */
#scroller>div {
width: 0px;
height: 101%;
}
/* The 'remaining width' container (= screenwidth - scrollbar, equals innerwidth of scrollbox) */
#THIRD{
display: table-cell;
width: 100%;
height: 0px;
}
/* Needed to give the children a 'width' reference */
#THIRD_A{
position: relative;
width: 100%;
height: 0px;
}
/* The actual header items */
#THIRD_B {
position: absolute;
top: 0;
left: 50%;
width: 25px;
height: 220px;
background: black;
}
#THIRD_C {
position: absolute;
top: 0;
left: 10%;
width: 125px;
height: 120px;
background: black;
}
#THIRD_D {
position: absolute;
top: 0;
right: 0%;
width: 25px;
height: 220px;
background: black;
}
NOTE:
On most handheld browser, this is 1px off. It seems webkit based browsers display a tablecell of 0px width as 1px width (see this question). The solution is to wrap the table in another div with css:
position absolute;
left: 0;
right: -1px
and setting #scroller>div to a width of 1px.
NOTE 2:
This is also a solution for a fixed div inside a scrollable div. See this fiddle

Position element to the bottom of a height: 100% div

I would like to "pin" a button to the bottom of a sidebar-div that has a height of 100%, as it should fill the whole left side of the page.
I tried to do it this way:
.sidebar {
height: 100%;
position: relative;
background-color: #CCCCCC;
}
.btn {
position: absolute;
bottom:0px;
top: auto;
}
<div class="sidebar">
<button class="btn">Button</button>
</div>
It might be because of the height in percent, as it works with a Pixel-height, but there must be some way of getting this done with percent, as the sidebar must span the entire page height.
To fix this, give your html and body a height of 100% as follows. Right now they don't have a defined height set (so they are 0px high), so your button is technically already at the bottom.
Live Example:
html, body {
height: 100%;
}
.sidebar {
height: 100%;
position: relative;
background-color: #CCCCCC;
}
.btn {
position: absolute;
bottom:0;
}
<div class="sidebar">
<button class="btn">Button</button>
</div>
The issue is your container doesn't have any actual height. You'll need to define the height on both your html and body tags too to use percent height there.
html,
body {
height: 100%;
margin: 0;
}
.sidebar {
height: 100%;
position: relative;
background-color: #CCCCCC;
}
.btn {
position: absolute;
bottom: 0px;
top: auto;
}
<div class="sidebar">
<button class="btn">Button</button>
</div>

Make content div and top/bottom running marginals equal 100% total height?

Ok so I've got a header and a footer with absolute positioning and heights of 144px. The content div in the middle area needs to be the full height of the area in between.
Simplified:
<style>
.marginals{
position: absolute;
height: 144px;
width: 100%;
left: 0;
}
#header{ top: 0px; }
#footer{ bottom: 0px; }
</style>
<div id="header" class="marginals"></div>
<div id="content"> Content </div>
<div id="footer" class="marginals"></div>
So basically I want a div that is 100% - 288px. At first I thought I could just make a 100% x 100% div with 144 padding on top and bottom and then stuff the content div in there at 100% but my thinking has gone stray somewhere.
Here's an example I made using 20% height for 'bread layers'. (Which I can't do on this project) Used 60% height for the scrolling 'meaty layer' and put it at top: 20%;
What you have won't work, tables and absolute positioning don't go well together, and height on table rows and cells is not handled consistently across browser anyway so I think you'd find it hard to get the top/bottom rows to stay a fixed height while still asking the middle row to scroll
however I think you were right with your original posting and using absolute positioning, you don't need percentages though, you can use the top and bottom co-ordinates tohether, so you can tell the middle div to start at 144px from top and finish 144px from bottom..
e.g.
HTML:
<div class="header">Some header content</div>
<div class="wrap">
Bulk content<br>bulk content<br>bulk content<br>bulk content<br>
Bulk content<br>bulk content<br>bulk content<br>bulk content
</div>
<div class="footer">Some footer content</div>
CSS:
html, body {margin: 0; padding: 0; height: 100%; overflow: hidden;}
.wrap {
position: absolute;
left: 0;
top: 144px; /* = height of header including any borders or padding */
bottom: 144px; /* = height of footer including any borders or padding */
width: 100%;
background: #fff;
overflow: auto;
}
.header, .footer {
position: absolute;
width: 100%;
left: 0;
height: 140px;
background: #f00;
}
.header {
top: 0;
border-bottom: 4px solid #000;
}
.footer {
bottom: 0;
border-top: 4px solid #000;
}
The whole thing is based on the html, body elements having the height of 100% set
Example: here
Looks like you're trying to create a 3 liquid row-layout. Why not try something like this:
Fiddle: http://jsfiddle.net/jCjsD/2/
HTML
<body>
<div id="body_container">
<div id="header">Some header content</div>
<div id="content"><!-- Bulk content here !--></div>
</div>
<div id="footer">Footer</div>
</body>
CSS
#header, #content, #footer {
float: left;
width: 100%;
}
#header {
border-bottom: 1px solid #888;
background: yellow;
}
#footer {
border-top: 1px solid #888;
background: yellow;
}
#content {
clear: both;
padding-bottom: 50px;
}
#footer {
position: relative;
margin-top: -50px;
height: 50px;
clear:both;
}
html, body {
height: 100%;
padding: 0;
margin: 0;
}
#body_container {
min-height: 100%;
}