Make position:sticky follow an element outside of its parent container? - css

So what I've got is a landing page that should show a.) a header b.) a large picture that fills the screen on desktop and c.) a navbar. The navbar should scroll along the page once it's reached the top of the page.
Now, the only way I would know to ensure that all three of those elements fit perfectly into the viewport on desktop is with max-height: 100vh in a parent element. However, I'd like to make the navbar scroll along once it's at the top of the screen. This isn't possible when the navbar is in its own container, because it'll stop scrolling along once it's hit the bottom of its container.
Does anyone know of a fix? Below is a sample of the structure I'm trying to use right now:
<div class="container-top">
<header>
<!--- ...header content... -->
</header>
<div>
<img src="1080p_image.png">
</div>
<nav class="sticky-top">
<!-- ...navbar content... -->
</nav>
</div>
<div class="other-content">
<!-- ...other content... -->
</div>
.container-top{
max-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
}
img{
width: 100vw;
}
header{
background-color: darkgray;
height: 5rem;
z-index: 1;
}
nav{
background-color: darkgray;
height: 5rem;
z-index: 1;
}
.sticky-top{
position: sticky;
top: 0;
}
.other-content{
height: 100rem;
}

Have you tried moving your nav into its own div below div.container-top?
<div class="container-top">
<header>
<!-- ...header content -->
</header>
<div>
<img/>
</div>
</div>
<div>
<nav class="sticky-top">
<!-- ..navbar content... -->
</nav>
</div>
<div class="other-content">
<!-- ...other content... -->
</div>
And if you want these elements to take up 100vh:
.container-top {
max-height: 75vh;
display: flex;
flex-direction: column;
justify-content: space-between;
}
nav {
max-height: 25vh;
}
Lemme know how that works for ya.

Related

Why does the sticky element not stick to the top when the flex element is full-width?

I have a flex div that contains two further elements. When viewed fullscreen on a desktop, one of the elements acts as a sidebar. On smaller screens, the elements collapse to be displayed one on top of the other.
This technique is explained on Every Layout.
I want to introduce a sticky element that will be used for navigation. On a wide screen, it works as expected. I can scroll the page and the sitcky element sticks to the top. However, in a narrower window, the element does not stick. It scrolls out of view – the same in both Safari and Firefox.
.with-sidebar {
display: flex;
flex-wrap: wrap;
}
.with-sidebar > :first-child {
flex-basis: 20rem;
flex-grow: 1;
background-color: green;
}
.with-sidebar > :last-child {
flex-grow: 999;
min-inline-size: 50%;
background-color: red;
}
.sticky {
position: sticky;
top: 1rem;
}
<div class="with-sidebar">
<div>
<h1>Sidebar</h1>
<div style="height:10rem">Spacer</div>
<div class="sticky">
<h1>Sticky Element</h1>
</div>
</div>
<div>
<h1>Not Sidebar</h1>
<div style="height:200rem">Spacer</div>
</div>
</div>
Among other things, I have tried wrapping the sticky inside another element, tried applying align-self: flex-start; to the sticky. I haven't yet found anything that works.
How can I ensure that the element is sticky when the sidebar and not-sidebar are stacked vertically as well as when they are alongside each other?
Update
I have experimented with placing .with-sidebar within a taller wrapper. Now it is clear what is happening. The element which is not the sidebar is pushing the sticky element off screen. This never happens when the elements are side by side. But, in a smaller window, the not-sidebar element is directly beneath the sticky element.
<div style="height: 400rem">
<div class="with-sidebar">
<div>
<h1>Sidebar</h1>
<div style="height:10rem">Spacer</div>
<div class="sticky">
<h1>Sticky Element</h1>
</div>
</div>
<div>
<h1>Not Sidebar</h1>
<div style="height:60rem">Spacer</div>
</div>
</div>
</div>
I think it's better to redo the markup (or design). See, for example, if you specify a height for a block, in which the "sticky element" is located, div:has(.sticky) { height: 500px; } then the element starts to "stick a little", or another example, change nesting
<body>
<div class="with-sidebar">
<div>
<h1>Sidebar</h1>
<div style="height: 10rem">Spacer</div>
<!-- <div class="sticky">
<h1>Sticky Element</h1>
</div> -->
</div>
<div class="sticky">
<h1>Sticky Element</h1>
</div>
<div>
<h1>Not Sidebar</h1>
<div style="height: 200rem">Spacer</div>
</div>
</div>
</body>
and add a property align-self for this element.
.sticky {
position: sticky;
top: 1rem;
align-self: flex-start;
}
But I don't think that's all for you. This is just an examples so you can see how the element works. The specification CSS Positioned Layout Module Level 3 very briefly describes the behavior of elements when positioned sticky.
Use media queries for top position if 10rem is not as you want...
.sticky {
position: fixed;
top: 10rem;
}
Update: I clarify my thoughts:
<div class="with-sidebar">
<div>
<h1>Sidebar</h1>
<div style="height:10rem">Spacer</div>
<div class="sticky_desktop">
<h1>Sticky Element</h1>
</div>
</div>
<div>
<div class="sticky_mobile">
<h1>Sticky Element</h1>
</div>
<h1>Not Sidebar</h1>
<div style="height:200rem">Spacer</div>
</div>
</div>
.with-sidebar {
display: flex;
flex-wrap: wrap;
}
.with-sidebar > :first-child {
flex-basis: 20rem;
flex-grow: 1;
background-color: green;
}
.with-sidebar > :last-child {
flex-grow: 999;
min-inline-size: 50%;
background-color: red;
}
.sticky_desktop {
position: sticky;
top: 1rem;
}
.sticky_mobile {
position: sticky;
top: 1rem;
color: violet;
}
.sticky_mobile>h1{
margin-block-start: 0;
margin-block-end: 0;
}
#media screen and (min-width : 656px ){
.sticky_mobile {
display:none;
}
}
#media screen and (max-width : 655px ){
.sticky_desktop {
display:none;
}
}

CSS Grid - How to implement grid items, which don't "stretch" the grid row and have a scroll-able container

My challenge is:
I want to have a grid with a fixed amount of columns (which can later be adjusted via javascript) and a flexible amount of rows of equal height.
The number of rows are determined by the amount of grid items, which are UI cards.
These cards should fill out the entire height of their respective cell but MUST not increase the height of the row. So basically max-height = row-height assigned by grid
Then inside these cards we have the typical three parts: Header, Body and Footer. The body MUST be scroll-able, if more list items exists than the row-height allows.
I've tried to implement this on stackblitz
https://stackblitz.com/edit/angular-3gkmtm
What i don't understand is
Why the cards "stretches" the row when more items appear
How to achieve the scroll-able card body section without manually using a fixed height (like in the example i use max-height)
Why when there are more then 3 rows, it overflows
Please help!
<article>
<section>
<h2>Fixed Gird with scrollable cards</h2>
</section>
<section>
<button (click)="onAdd()">Add</button>
<button (click)="onRemove()">Remove</button>
</section>
<section class="remaining-height">
<div class="grid-container">
<div class="grid-item" *ngFor="let card of cards">
<div class="card">
<div class="card-header">Card #{{card}}</div>
<div class="card-body card-flexible-scroll">
<div class="list-item" *ngFor="let item of list">{{item}}</div>
</div>
<div class="card-footer">
Some Footer
</div>
</div>
</div>
</div>
</section>
</article>
article{
display: flex;
flex-direction: column;
height: 100%;
}
.remaining-height{
flex:1
}
.grid-container {
display: grid;
grid-gap: 0.5rem;
height: 100%;
grid-auto-rows: auto;
grid-template-columns: repeat(5, auto);
}
.grid-item{
display: flex;
padding:24px;
}
.card{
display: flex;
flex-direction: column;
width: 100%;
height:100%;
background:#ccc;
}
.card-body{
.list-item{
padding: 6px;
background:#fcd3d3;
}
.list-item:nth-child(even){
background:#efefef;
}
}
.card-flexible-scroll{
flex:1;
overflow-y:auto;
max-height: 300px; // <= no max height
}
Angular Controller to generate cards and list items
```js
export class AppComponent {
name = "Angular";
cards = new Array(8).fill(0).map((_,idx)=>idx+1);
list = new Array(30).fill(0).map((_,idx)=>idx+1);
onAdd() {
this.cards.push(this.cards.length + 1);
}
onRemove() {
this.cards.pop();
}
}
global style
html , body{
height: 100vh;
overflow: hidden;
}
If I understand correctly what you are trying to do you want that the card body takes all the available row space between the header and footer and not force the card to be bigger than the row if it contains items.
It is possible to achieve that with adding another div with absolute positioning inside the card body div that is sized to the full body height then the items inside will overflow correctly.
Here is the changed template:
<article>
<section>
<h2>Fixed Gird with scrollable cards</h2>
</section>
<section>
<button (click)="onAdd()">Add</button>
<button (click)="onRemove()">Remove</button>
</section>
<section class="remaining-height">
<div class="grid-container">
<div class="grid-item" *ngFor="let card of cards">
<div class="card">
<div class="card-header">Card #{{card}}</div>
<div class="card-body">
<div class="card-flexible-scroll">
<div class="list-item" *ngFor="let item of list">{{item}}</div>
</div>
</div>
<div class="card-footer">
Some Footer
</div>
</div>
</div>
</div>
</section>
</article>
And the updated CSS:
.card-body{
.list-item{
padding: 6px;
background:#fcd3d3;
}
.list-item:nth-child(even){
background:#efefef;
}
position: relative;
flex:1;
}
.card-flexible-scroll{
position: absolute;
width: 100%;
height: 100%;
max-height: 100%;
overflow-y: auto;
}
I created a fork of your StackBlitz where you can see how it works. If this is not what you look for please explain more.

How to fix buttons above footer using flex

I am using Materializecss to create React app. I have already applied flex inside my app class. And now I want to apply flex inside main tag where buttons get fixed at bottom of main tag & above the footer.
I have tried {margin-top: auto;} , justify-content: flex-end which didn't help. The buttons always print after content class. I can set the height of the content class, but small devices render view badly and it's not fixing my problem.
JSX code:
<div className="app">
<header> <header>
<main>
<div className="box">
<div className="content"> Long text less than 100 words <div>
<div className="buttons"> <button> Button-1 </button> <button> Button-2 </button>
<div>
<main>
<footer><footer>
<div>
My css
app {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
I want to stick my button above the footer. My content class has 100 words then the button should stick above the footer not rendered after the content class.
I would appreciate the help.
You haven't applied display:flex etc to the main element. If you do that the margin-top:auto will work.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
::before,
::after {
box-sizing: inherit;
}
.app {
display: flex;
min-height: 100vh;
flex-direction: column;
background: ;
}
main {
flex: 1;
display: flex;
flex-direction: column;
}
.buttons {
margin-top: auto;
}
<div class="app">
<header>header </header>
<main>
<div class="box">box</div>
<div class="content"> Long text less than 100 words </div>
<div class="buttons">
<button> Button-1 </button>
<button> Button-2 </button>
</div>
</main>
<footer>footer</footer>
</div>

Flexbox Style Only Works When Manually Entered in Chrome Developer Tools

I am trying to get an ag-Grid to fill the vertical space in a panel on a page. If I put the style on the .HTML page like this:
<style>
.right_panel > div {
display: flex;
flex: 1;
height: 95%;
flex-direction: column;
}
</style>
... or include it in the Javascript:
$(".right_panel > div").css("display", "flex");
$(".right_panel > div").css("flex-direction", "column");
$(".right_panel > div").css("height", "95%");
$(".right_panel > div").css("flex", "1");
... the styles will correctly show up in the Chrome CSS Debugger:
... but my ag-Grid is crushed down to zero height.
{image of a page with a grid that only has a navbar under which there's a lot of white space}
Disabling them and retyping them into the CSS Debugger verbatim:
... and the grid works fine
{image of a page with a grid that properly fills the page}
Is there something obvious (obviously) that I'm missing? I've read questions like this one to get the grid to fill the available vertical space. Why does this seemingly correct css only work when manually entered in the CSS debugger, and how do I fix it in my code?
Heavily edited HTML for the page:
<html>
<head>...</head>
<body>
<div id="main-view" ui-view="" class="ng-scope">
<div class="vbox ng-scope">
<div class="vbox-grow page-sidebar ng-scope splitter_panel" split-v="splitterCtrl">
<main class="hbox-grow right_panel" style="width: 1233px;">
<div ui-view="" class="ng-scope"><div class="task page ng-scope"> <!-- THIS is the line with the CSS I'm editing -->
<div class="task page ng-scope">
<article class="page-content">
<ui-view class="ng-scope">
<article class="page-content ng-scope" style="height: 100%; background-color: aliceblue; display: flex; flex-direction: column; flex: 1;">
<div id="gridContainer" style="height: 100%; background-color: yellow; flex-direction: column; flex: 1 1 0%; display: flex;">
<!-- THIS is the div that gets populated with the ag-grid -->
<div id="gridContent" style="width: 100%; background-color: hotpink; flex: 1; display: flex; flex-direction: column;" class="ag-fresh">

How to put a div fix at bottom of the page && above footer using css

I have a box (shopping cart) which I want it to be fixed at the bottom of the page.
But if the page is not long enough, or the user scroll to the end of the page, so that the footer is in the viewport. In this case I want the shopping cart box to be right above the footer.
Is it possible to do this without JS?
Thanks
answer only for the fun, you should relay on javascript to make it solid.
There could be a risky trick with position:sticky, floatting pseudo and display resetting.
aside,
footer {
position: sticky;
bottom: 0;
background: white;
border: solid;
clear: both;
z-index: 1;
}
footer {
z-index: 0;/*hide it from aside */
}
html:before {/* push sticky elements down */
content: "";
float: left;
height: 100vh;
}
body {
margin:0;
display: inline;/* get body scrolling and block formatting context off the game */
}
<main>main<br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> end main</main>
<aside>aside at bottom !</aside>
<footer>footer</footer>
You can play a little with and test it with content, but remenber, its not reliable, sticky not implemented everywhere and behavior can varie from a browser to another, from an update to another in the most funny and unexpected ways.
Your question is not very clear, so I'll try to answer it as best as I understand.
Getting your footer to sit on the bottom of a short page
To achieve this effect, flexbox comes in very handy. First, set the
body's style to : display:flex;
flex-direction: column;
min-height: 100vh;
Setting display:flex makes all child elements on the page (i.e. in the body tag) flex items, hence offering us the opportunity to style said elements as such. Setting flex-direction to column also ensures that all child elements of body are stacked vertically.
Now, the min-height: 100vh; makes the body span at least 100% of the viewport height.
To get an element (in this case your shopping cart box. Let's call it .shopping-cart-box from here on out) to "push" the footer to the bottom of the viewport, you can then set it's flex (or flex-grow, to be precise) value to 1. This makes it take up the rest of the available space.
Remember the body is at 100% of viewport height. If your navigation menu takes up, say, 10% and footer takes up 5% of the viewport height respectively, the .shopping-cart-box with flex: 1; will then fill up the rest of the 85% of viewport height, essentially "pushing" the footer to the bottom of the page.
Making your shopping cart box sit on top of the footer
Now that we have the footer at the bottom and .shopping-cart-box taking up the rest of the available space, we need to force its contents to align bottom.
We can do this by adding display: flex;
flex-direction: column; to .shopping-cart-box. That, again, allows us to treat all child elements of .shopping-cart-box as flex items.
Assuming we have a child element .shopping-cart-product inside .shopping-cart-box, we can set align it to the bottom of its parent container in two ways:
using margin-top: auto or
align-self: flex-end
A visual representation can be found below with the provided code snippet:
/* RELEVANT CODE*/
.body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.shopping-cart-box {
display: flex;
flex: 1;
}
.shopping-cart-product {
margin-top: auto; /* or align-self: flex-end; */
}
/*CODE FOR AESTHETICS */
.container {
width: 90%;
margin-left: auto;
margin-right: auto;
}
.nav {
display: flex;
flex-direction: row;
padding: 12px;
}
.logo {
display: block;
margin-right: auto;
}
/* Footer */
.footer {
background-color: #013940;
padding: 18px 12px;
color: white;
margin-top: 6px;
}
.ui.item.menu,
.ui.item.menu .item {
width: 40% !important;
margin: 0;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/semantic.min.css" rel="stylesheet" />
<body class="body">
<header>
<nav class="nav">
<a href="#" class="logo">
<h1>
ShopperStack</h1>
</a>
<ul class="ui two item menu nav--links">
<li class="item">
Home
</li>
<li class="item">
About
</li>
</ul>
</nav>
</header>
<div class="container">
<p>
This is a block of text on the upper part of the page
</p>
</div>
<div class="shopping-cart-box">
<div class="shopping-cart-product container">
<form action="" class="ui segment form">
<div class="field">
<h3 class="header">Place Order</h3>
<p class="label">ACME SuperBlaster 4000</p>
<input type="submit" value="Buy Now" class="ui button">
</div>
</form>
</div>
</div>
<footer class="footer">
<section class="container">
<h3>Thanks for visiting ShopperStack!</h3>
</section>
</footer>
</body>

Resources