Related
I want to make the navigation bar stick to the top of the viewport once a user scrolls the page, but it's not working and I have no clue why. If you can please help, here is my HTML and CSS code:
.container {
min-height: 300vh;
}
.nav-selections {
text-transform: uppercase;
letter-spacing: 5px;
font: 18px "lato",sans-serif;
display: inline-block;
text-decoration: none;
color: white;
padding: 18px;
float: right;
margin-left: 50px;
transition: 1.5s;
}
.nav-selections:hover{
transition: 1.5s;
color: black;
}
ul {
background-color: #B79b58;
overflow: auto;
}
li {
list-style-type: none;
}
<main class="container">
<nav style="position: sticky; position: -webkit-sticky;">
<ul align="left">
<li>Contact</li>
<li>About</li>
<li>Products</li>
<li>Home</li>
</ul>
</nav>
</main>
Check if an ancestor element has overflow set (e.g. overflow:hidden); try toggling it. You may have to inspect the DOM tree higher than you expect =).
This may affect your position:sticky on a descendant element.
Sticky positioning is a hybrid of relative and fixed positioning. The element is treated as relative positioned until it crosses a specified threshold, at which point it is treated as fixed positioned.
...
You must specify a threshold with at least one of top, right, bottom, or left for sticky positioning to behave as expected. Otherwise, it will be indistinguishable from relative positioning.
[source: MDN]
So in your example, you have to define the position where it should stick in the end by using the top property.
html, body {
height: 200%;
}
nav {
position: sticky;
position: -webkit-sticky;
top: 0; /* required */
}
.nav-selections {
text-transform: uppercase;
letter-spacing: 5px;
font: 18px "lato", sans-serif;
display: inline-block;
text-decoration: none;
color: white;
padding: 18px;
float: right;
margin-left: 50px;
transition: 1.5s;
}
.nav-selections:hover {
transition: 1.5s;
color: black;
}
ul {
background-color: #B79b58;
overflow: auto;
}
li {
list-style-type: none;
}
<nav>
<ul align="left">
<li>Contact</li>
<li>About</li>
<li>Products</li>
<li>Home</li>
</ul>
</nav>
I have same problem, and i found the answer here.
If your element isn't sticking as expected the first thing to check are the rules applied to the container.
Specifically, look for any overflow property set on any parents of the element. You can't use: overflow: hidden, overflow: scroll or overflow: auto on the parent of a position: sticky element.
Incase you came across this and your sticky is not working - try setting the parent to:
display: unset
Worked for me
Few more things I've come across:
When your sticky element is a component (angular etc)
If the 'sticky' element itself is a component with a custom element-selector, such as an angular component named <app-menu-bar> you will need to add the following to the component's css:
:host { display: block; } // or use flexbox
or
app-menu-bar { display: block; } // (in the containing component's css)
Safari on iOS in particular seems to require `display:block` even on the root element `app-root` of an angular application or it won't stick.
If you are creating a component and defining the css inside the component (shadow DOM / encapsulated styles), make sure the position: sticky is being applied to the 'outer' selector (eg. app-menu-bar in devtools should show the sticky position) and not a top level div within the component. With Angular, this can be achieved with the :host selector in the css for your component.
:host
{
position: sticky;
display: block; // this is the same as shown above
top: 0;
background: red;
}
Other
If the element following your sticky element has a solid background, you must add the following to stop it from sliding underneath:
.sticky-element { z-index: 100; }
.parent-of-sticky-element { position: relative; }
Your sticky element must be before your content if using top and after it if using bottom.
There are complications when using overflow: hidden on your wrapper element – in general it will kill the sticky element inside. Better explained in this question
Mobile browsers may disable sticky/fixed positioned items when the onscreen keyboard is visible. I'm not sure of the exact rules (does anybody ever know) but when the keyboard is visible you're looking at a sort of 'window' into the window and you won't easily be able to get things to stick to the actual visible top of the screen.
Make sure you have:
position: sticky;
and not
display: sticky;
Misc usability concerns
Be cautious if your design calls for for sticking things to the bottom of the screen on mobile devices. On iPhone X for instance they display a narrow line to indicate the swipe region (to get back to the homepage) - and elements inside this region aren't clickable. So if you stick something there be sure to test on iPhone X that users can activate it. A big 'Buy Now' button is no good if people can't click it!
If you're advertising on Facebook the webpage is displayed in a 'webview' control within Facebook's mobile apps. Especially when displaying video (where your content begins in the bottom half of the screen only) - they often completely mess up sticky elements by putting your page within a scrollable viewport that actually allows your sticky elements to disappear off the top of the page. Be sure to test in the context of an actual ad and not just in the phone's browser or even Facebook's browser which can all behave differently.
This is a continuation of the answers from MarsAndBack and Miftah Mizwar.
Their answers are correct. However, it is difficult to identify the problem ancestor(s).
To make this very simple, simply run this jQuery script in your browser console and it will tell you the value of the overflow property on every ancestor.
$('.your-sticky-element').parents().filter(function() {
console.log($(this));
console.log($(this).css('overflow'));
return $(this).css('overflow') === 'hidden';
});
Where an ancestor does not have overflow: visible change its CSS so that it does!
Also, as stated elsewhere, make sure your sticky element has this in the CSS:
.your-sticky-element {
position: sticky;
top: 0;
}
I had to use the following CSS to get it working:
.parent {
display: flex;
justify-content: space-around;
align-items: flex-start;
overflow: visible;
}
.sticky {
position: sticky;
position: -webkit-sticky;
top: 0;
}
If above dosen't work then...
Go through all ancestors and make sure none of these elements have overflow: hidden. You have to change this to overflow: visible
Sticky Position will not work if your parent is using display flex. As I read this in one solution
Since flex box elements default to stretch, all the elements are the same height, which can't be scrolled against.
So if you are using display: flex; on parent then you will have to add this to sticky element align-self: flex-start; and also set height to auto height: auto;
This is how sticky element class look like
.stick-ontop {
position: -webkit-sticky !important; // for safari
position: sticky !important;
top: 0;
align-self: flex-start;
height: auto;
}
Another very common scenario where position: sticky might not be working is if it's parent has display: flex or display: grid.
What happens in this case is the sticky position is working but you can't see it bcoz the element is stretched completely. Try reducing it's height using align-self: baseline and you'll see the effect.
Attack this Q from other direction.
Imagine this is a game Find the nearest scrolling ancestor.
<!-- sticky not working -->
<h1 style="position: sticky; top:0;">Hello World</h1>
Questions:
1/3: The sticky node? <h1>.
2/3: The ancestor? <body>.
3/3: <body> scrolling ? FALSE => "No effect".
Fix: "Sticky is working" (<body> scrolling ? TRUE).
body{
min-height: 300vh;
}
<!-- sticky working -->
<h1 style="position: sticky; top: 0;">Hello World</h1>
With this in mind - here are some "hello world" "famous" scenarios of "not working" sticky :) Most cases relate to one or many of these cases.
Case 1: Missing "top" (Easy to fix):
Not working:
/* not working example */
aside{
position: sticky;
background: lightgray;
}
main{
height: 200vh;
}
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
Fix (Add top):
aside{
position: sticky;
top: 0;
}
main{
height: 200vh;
}
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
Case 2: Sticky node & overflow (Easy to fix):
I "destroy" the sticky by adding #extra-wrapper with overflow setting auto -or- hidden -or- visible - but without any clipped content.
"The problem" now the nearest scrolling ancestor (#extra-wrapper) "without" any scrolling (No scrollbar dragging option == "no scrolling ancestor").
Not working:
/* not working example */
#overflow-wrapper{
overflow: scroll;
}
aside{
position: sticky;
background: lightgray;
top: 0px;
}
main{
height: 200vh;
}
<div id="overflow-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
Fix - Clip the content (Now their is "nearest scrolling ancestor").
Working:
/* not working example */
#overflow-wrapper{
overflow: scroll;
max-height: 60vh; /* clip the content */
}
aside{
position: sticky;
background: lightgray;
top: 0px;
}
main{
height: 200vh;
}
<div id="overflow-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
Case 3: Sticky related to "wrong / not scrolling" node (Tricky to fix)
Again, Sticky offset relative to its nearest scrolling ancestor.
I "destroy" the sticky by adding #extra-wrapper to the sticky element. Why it is not working? Now the height of #extra-wrapper == height aside content (box model) == "no scrolling ancestor" == "no effect".
Not working:
/* not working example */
aside{
position: sticky;
top: 0;
background: lightgray;
}
main{
height: 200vh;
}
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
This is what really "happens" (I added height to #extra-wrapper):
#extra-wrapper{
background: lightgray;
height: 40vh;
}
aside{
position: sticky;
top: 0;
}
main{
height: 200vh;
}
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
FIX:
change the sticky node:
#extra-wrapper{
position: sticky;
top: 0;
}
aside{
}
#layout{
displ
}
main{
height: 200vh;
}
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
Case 4: display: flexbox/Grid layout - even cols by deafult (Tricky to fix)
You create flex/grid layout & set one of the cols to be sticky. By default the cols height is even = The height of the "nearest ancestor" (wrapper) == Cols height = no scroll effect.
Not working:
#extra-wrapper{
position: sticky;
top: 0;
border: 1px solid red;
}
aside{
}
#layout{
display: flex;
}
main{
height: 200vh;
}
<div id="layout">
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
FIX: Set the sticky aside max-height to be 90vh for example (Now the cols height is not even).
Working:
#extra-wrapper{
position: sticky;
top: 0;
border: 1px solid red;
max-height: 90vh;
}
aside{
}
#layout{
display: flex;
}
main{
height: 200vh;
}
<div id="layout">
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
It seems like that the navbar to be stickied shouldn't be inside any div or section with other content. None of the solution were working for me until I took the navbar out of the div which the navbar shared with another topbar .I previously had topbar and navbar wrapped with a common div.
I know this seems to be already answered, but I ran into a specific case, and I feel most answers miss the point.
The overflow:hidden answers cover 90% of the cases. That's more or less the "sticky nav" scenario.
But the sticky behavior is best used within the height of a container. Think of a newsletter form in the right column of your website that scrolls down with the page.
If your sticky element is the only child of the container, the container is the exact same size, and there's no room to scroll.
Your container needs to be the height you expect your element to scroll within. Which in my "right column" scenario is the height of the left column.
The best way to achieve this is to use display:table-cell on the columns. If you can't, and are stuck with float:right and such like I was, you'll have to either guess the left column height of compute it with Javascript.
I know it's too late. But I found a solution even if you are using overflow or display:flex in parent elements sticky will work.
steps:
Create a parent element for the element you want to set sticky (Get sure that the created element is relative to body or to full-width & full-height parent).
Add the following styles to the parent element:
{
position: absolute;
height: 100vmax;
}
For the sticky element, get sure to add z-index that is higher than all elements in the page.
That's it! Now it must work. Regards
1.Position sticky will most probably not work if overflow is set to hidden, scroll, or auto on any of the parents of the element.
2.Position sticky may not work correctly if any parent element has a set height.
Funny moment that wasn't obvious for me: at least in Chrome 70 position: sticky is not applied if you've set it using DevTools.
I know this is an old post. But if there's someone like me that just recently started messing around with position: sticky this can be useful.
In my case i was using position: sticky as a grid-item. It was not working and the problem was an overflow-x: hidden on the html element. As soon as i removed that property it worked fine. Having overflow-x: hidden on the body element seemed to work tho, no idea why yet.
from my comment:
position:sticky needs a coordonate to tel where to stick
nav {
position: sticky;
top: 0;
}
.nav-selections {
text-transform: uppercase;
letter-spacing: 5px;
font: 18px "lato", sans-serif;
display: inline-block;
text-decoration: none;
color: white;
padding: 18px;
float: right;
margin-left: 50px;
transition: 1.5s;
}
.nav-selections:hover {
transition: 1.5s;
color: black;
}
ul {
background-color: #B79b58;
overflow: auto;
}
li {
list-style-type: none;
}
body {
height: 200vh;
}
<nav>
<ul align="left">
<li>Contact</li>
<li>About</li>
<li>Products</li>
<li>Home</li>
</ul>
</nav>
There is polyfill to use for other browsers than FF and Chrome . This is an experimental rules that can be implemented or not at any time through browsers. Chrome add it a couple of years ago and then dropped it, it seems back ... but for how long ?
The closest would be position:relative + coordonates updated while scrolling once reached the sticky point, if you want to turn this into a javascript script
two answer here:
remove overflow property from body tag
set height: 100% to the body to fix the problem with overflow-y: auto
min-height: 100% not-working instead of height: 100%
I believe this article say a lot about how sticky works
How CSS Position Sticky Really Works!
CSS position sticky has two main parts, sticky item & sticky container.
Sticky Item — is the element that we defined with the position: sticky styles. The element will float when the viewport position
matches the position definition, for example: top: 0px .
Sticky Container —is the HTML element which wraps the sticky item. This is the maximum area that the sticky item can float in.
When you define an element with position: sticky you’re automatically
defining the parent element as a sticky container!
z-index is also very important. Sometimes it will work but you just won't see it. Try setting it to some very high number just to be sure. Also don't always put top: 0 but try something higher in case it's hidden somewhere (under a toolbar).
The real behavior of a sticky element is:
First it is relative for a while
then it is fixed for a while
finally, it disappears from the view
A stickily positioned element is treated as relatively positioned until its containing block crosses a specified threshold (such as setting top to value other than auto) within its flow root (or the container it scrolls within), at which point it is treated as "stuck" until meeting the opposite edge of its containing block.
The element is positioned according to the normal flow of the document, and then offset relative to its nearest scrolling ancestor and containing block (nearest block-level ancestor), including table-related elements, based on the values of top, right, bottom, and left. The offset does not affect the position of any other elements.
This value always creates a new stacking context. Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hidden, scroll, auto, or overlay), even if that ancestor isn't the nearest actually scrolling ancestor.
This example will help you understand:
code https://codepen.io/darylljann/pen/PpjwPM
Using the strategy from this blog (https://www.designcise.com/web/tutorial/how-to-fix-issues-with-css-position-sticky-not-working) I came up with an option for those that can't have control over all components in the page
I'm using Angular and in the ngOnInit method I run this code to change the visible propertys of parents to visible
/**
* position: sticky
* only works if all parent components are visibile
*/
let parent = document.querySelector('.sticky').parentElement;
while (parent) {
const hasOverflow = getComputedStyle(parent).overflow;
if (hasOverflow !== 'visible') {
parent.style.overflow = 'visible';
// console.log(hasOverflow, parent);
}
parent = parent.parentElement;
}
Watch out for empty grid areas!
In my case I had something like this:
.cart-areas {
grid-template-columns: 2fr 0.2fr 1.5fr;
grid-template-areas:
'order . summary'
'order . .'
'order . .';
}
I wanted that summary grid item to be sticky when the user completes the checkout form. It didn't work because of those empty grid items (marked with .).
The solution was to delete those empty items like so:
.cart-areas {
grid-template-columns: 2fr 0.2fr 1.5fr;
grid-template-areas:
'order . summary'
'order . summary'
'order . summary';
}
if danday74's fix doesn't work, check that the parent element has a height.
In my case I had two childs, one floating left and one floating right.
I wanted the right floating one to become sticky but had to add a <div style="clear: both;"></div> at the end of the parent, to give it height.
I used a JS solution. It works in Firefox and Chrome. Any problems, let me know.
html
<body>
<header id="header">
<h1>Extra-Long Page Heading That Wraps</h1>
<nav id="nav">
<ul>
<li>Home</li>
<li>Page 2</li>
<li>Page 3</li>
</ul>
</nav>
</header>
<main>
<p><!-- ridiculously long content --></p>
</main>
<footer>
<p>FOOTER CONTENT</p>
</footer>
<script src="navbar.js" type="text/javascript"></script>
</body>
css
nav a {
background: #aaa;
font-size: 1.2rem;
text-decoration: none;
padding: 10px;
}
nav a:hover {
background: #bbb;
}
nav li {
background: #aaa;
padding: 10px 0;
}
nav ul {
background: #aaa;
list-style-type: none;
margin: 0;
padding: 0;
}
#media (min-width: 768px) {
nav ul {
display: flex;
}
}
js
function applyNavbarSticky() {
let header = document.querySelector('body > header:first-child')
let navbar = document.querySelector('nav')
header.style.position = 'sticky'
function setTop() {
let headerHeight = header.clientHeight
let navbarHeight = navbar.clientHeight
let styleTop = navbarHeight - headerHeight
header.style.top = `${styleTop}px`
}
setTop()
window.onresize = function () {
setTop()
}
}
Here's what was tripping ME up... my sticky div was inside another div so that parent div needed some additional content AFTER the sticky div, to make the parent div "tall enough" for the sticky div to "slide over" other content as you scroll down.
So in my case, right after the sticky div, I had to add:
%div{style:"height:600px;"}
(My application has two side-by-side divs, with a "tall" image on the left, and a short data entry form on the right, and I wanted the data entry form to float next to the image as you scroll down, so the form is always on the screen. It would not work until I added the above "extra content" so the sticky div has something to "slide over"
I had the same problem. For me the problem was display: 'none' on big screens (media-query) and display: 'initial' on smartphones. If i removed the display css property and added opacity and pointer events none on desktop everything workedout.
Despite reading this entire page and trying everything but the kitchen sink this simply doesn't work on my mobile device. No sticky action at all -- and that's the only place I need it to work. I have ruled out any special designation or overrides that would stop this from functioning for this screen width. Cleared my mobile formatting code as a test and no change was seen. Works perfectly on Chrome browser on my laptop and not at all on chrome browser on my new S10.
It's TRUE that the overflow needs to be removed or set to initial to make position: sticky works on the child element. I used Material Design in my Angular app and found out that some Material components changed the overflow value. The fix for my scenario is
mat-sidenav-container, mat-sidenav-content {
overflow: initial;
}
One of the most common mistakes when going about the position sticky is:
Browser not supporting it
Not including any top bottom right left properties. Bare in mind that the Browser won't know how to handle that properly if you don't give it enough information. It will be just be statically positioned without it.
I have written about that and more insides on this article. Just putting a reference so I don't repeat myself too much.
I want to make the navigation bar stick to the top of the viewport once a user scrolls the page, but it's not working and I have no clue why. If you can please help, here is my HTML and CSS code:
.container {
min-height: 300vh;
}
.nav-selections {
text-transform: uppercase;
letter-spacing: 5px;
font: 18px "lato",sans-serif;
display: inline-block;
text-decoration: none;
color: white;
padding: 18px;
float: right;
margin-left: 50px;
transition: 1.5s;
}
.nav-selections:hover{
transition: 1.5s;
color: black;
}
ul {
background-color: #B79b58;
overflow: auto;
}
li {
list-style-type: none;
}
<main class="container">
<nav style="position: sticky; position: -webkit-sticky;">
<ul align="left">
<li>Contact</li>
<li>About</li>
<li>Products</li>
<li>Home</li>
</ul>
</nav>
</main>
Check if an ancestor element has overflow set (e.g. overflow:hidden); try toggling it. You may have to inspect the DOM tree higher than you expect =).
This may affect your position:sticky on a descendant element.
Sticky positioning is a hybrid of relative and fixed positioning. The element is treated as relative positioned until it crosses a specified threshold, at which point it is treated as fixed positioned.
...
You must specify a threshold with at least one of top, right, bottom, or left for sticky positioning to behave as expected. Otherwise, it will be indistinguishable from relative positioning.
[source: MDN]
So in your example, you have to define the position where it should stick in the end by using the top property.
html, body {
height: 200%;
}
nav {
position: sticky;
position: -webkit-sticky;
top: 0; /* required */
}
.nav-selections {
text-transform: uppercase;
letter-spacing: 5px;
font: 18px "lato", sans-serif;
display: inline-block;
text-decoration: none;
color: white;
padding: 18px;
float: right;
margin-left: 50px;
transition: 1.5s;
}
.nav-selections:hover {
transition: 1.5s;
color: black;
}
ul {
background-color: #B79b58;
overflow: auto;
}
li {
list-style-type: none;
}
<nav>
<ul align="left">
<li>Contact</li>
<li>About</li>
<li>Products</li>
<li>Home</li>
</ul>
</nav>
I have same problem, and i found the answer here.
If your element isn't sticking as expected the first thing to check are the rules applied to the container.
Specifically, look for any overflow property set on any parents of the element. You can't use: overflow: hidden, overflow: scroll or overflow: auto on the parent of a position: sticky element.
Incase you came across this and your sticky is not working - try setting the parent to:
display: unset
Worked for me
Few more things I've come across:
When your sticky element is a component (angular etc)
If the 'sticky' element itself is a component with a custom element-selector, such as an angular component named <app-menu-bar> you will need to add the following to the component's css:
:host { display: block; } // or use flexbox
or
app-menu-bar { display: block; } // (in the containing component's css)
Safari on iOS in particular seems to require `display:block` even on the root element `app-root` of an angular application or it won't stick.
If you are creating a component and defining the css inside the component (shadow DOM / encapsulated styles), make sure the position: sticky is being applied to the 'outer' selector (eg. app-menu-bar in devtools should show the sticky position) and not a top level div within the component. With Angular, this can be achieved with the :host selector in the css for your component.
:host
{
position: sticky;
display: block; // this is the same as shown above
top: 0;
background: red;
}
Other
If the element following your sticky element has a solid background, you must add the following to stop it from sliding underneath:
.sticky-element { z-index: 100; }
.parent-of-sticky-element { position: relative; }
Your sticky element must be before your content if using top and after it if using bottom.
There are complications when using overflow: hidden on your wrapper element – in general it will kill the sticky element inside. Better explained in this question
Mobile browsers may disable sticky/fixed positioned items when the onscreen keyboard is visible. I'm not sure of the exact rules (does anybody ever know) but when the keyboard is visible you're looking at a sort of 'window' into the window and you won't easily be able to get things to stick to the actual visible top of the screen.
Make sure you have:
position: sticky;
and not
display: sticky;
Misc usability concerns
Be cautious if your design calls for for sticking things to the bottom of the screen on mobile devices. On iPhone X for instance they display a narrow line to indicate the swipe region (to get back to the homepage) - and elements inside this region aren't clickable. So if you stick something there be sure to test on iPhone X that users can activate it. A big 'Buy Now' button is no good if people can't click it!
If you're advertising on Facebook the webpage is displayed in a 'webview' control within Facebook's mobile apps. Especially when displaying video (where your content begins in the bottom half of the screen only) - they often completely mess up sticky elements by putting your page within a scrollable viewport that actually allows your sticky elements to disappear off the top of the page. Be sure to test in the context of an actual ad and not just in the phone's browser or even Facebook's browser which can all behave differently.
This is a continuation of the answers from MarsAndBack and Miftah Mizwar.
Their answers are correct. However, it is difficult to identify the problem ancestor(s).
To make this very simple, simply run this jQuery script in your browser console and it will tell you the value of the overflow property on every ancestor.
$('.your-sticky-element').parents().filter(function() {
console.log($(this));
console.log($(this).css('overflow'));
return $(this).css('overflow') === 'hidden';
});
Where an ancestor does not have overflow: visible change its CSS so that it does!
Also, as stated elsewhere, make sure your sticky element has this in the CSS:
.your-sticky-element {
position: sticky;
top: 0;
}
I had to use the following CSS to get it working:
.parent {
display: flex;
justify-content: space-around;
align-items: flex-start;
overflow: visible;
}
.sticky {
position: sticky;
position: -webkit-sticky;
top: 0;
}
If above dosen't work then...
Go through all ancestors and make sure none of these elements have overflow: hidden. You have to change this to overflow: visible
Sticky Position will not work if your parent is using display flex. As I read this in one solution
Since flex box elements default to stretch, all the elements are the same height, which can't be scrolled against.
So if you are using display: flex; on parent then you will have to add this to sticky element align-self: flex-start; and also set height to auto height: auto;
This is how sticky element class look like
.stick-ontop {
position: -webkit-sticky !important; // for safari
position: sticky !important;
top: 0;
align-self: flex-start;
height: auto;
}
Another very common scenario where position: sticky might not be working is if it's parent has display: flex or display: grid.
What happens in this case is the sticky position is working but you can't see it bcoz the element is stretched completely. Try reducing it's height using align-self: baseline and you'll see the effect.
Attack this Q from other direction.
Imagine this is a game Find the nearest scrolling ancestor.
<!-- sticky not working -->
<h1 style="position: sticky; top:0;">Hello World</h1>
Questions:
1/3: The sticky node? <h1>.
2/3: The ancestor? <body>.
3/3: <body> scrolling ? FALSE => "No effect".
Fix: "Sticky is working" (<body> scrolling ? TRUE).
body{
min-height: 300vh;
}
<!-- sticky working -->
<h1 style="position: sticky; top: 0;">Hello World</h1>
With this in mind - here are some "hello world" "famous" scenarios of "not working" sticky :) Most cases relate to one or many of these cases.
Case 1: Missing "top" (Easy to fix):
Not working:
/* not working example */
aside{
position: sticky;
background: lightgray;
}
main{
height: 200vh;
}
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
Fix (Add top):
aside{
position: sticky;
top: 0;
}
main{
height: 200vh;
}
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
Case 2: Sticky node & overflow (Easy to fix):
I "destroy" the sticky by adding #extra-wrapper with overflow setting auto -or- hidden -or- visible - but without any clipped content.
"The problem" now the nearest scrolling ancestor (#extra-wrapper) "without" any scrolling (No scrollbar dragging option == "no scrolling ancestor").
Not working:
/* not working example */
#overflow-wrapper{
overflow: scroll;
}
aside{
position: sticky;
background: lightgray;
top: 0px;
}
main{
height: 200vh;
}
<div id="overflow-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
Fix - Clip the content (Now their is "nearest scrolling ancestor").
Working:
/* not working example */
#overflow-wrapper{
overflow: scroll;
max-height: 60vh; /* clip the content */
}
aside{
position: sticky;
background: lightgray;
top: 0px;
}
main{
height: 200vh;
}
<div id="overflow-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
Case 3: Sticky related to "wrong / not scrolling" node (Tricky to fix)
Again, Sticky offset relative to its nearest scrolling ancestor.
I "destroy" the sticky by adding #extra-wrapper to the sticky element. Why it is not working? Now the height of #extra-wrapper == height aside content (box model) == "no scrolling ancestor" == "no effect".
Not working:
/* not working example */
aside{
position: sticky;
top: 0;
background: lightgray;
}
main{
height: 200vh;
}
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
This is what really "happens" (I added height to #extra-wrapper):
#extra-wrapper{
background: lightgray;
height: 40vh;
}
aside{
position: sticky;
top: 0;
}
main{
height: 200vh;
}
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
FIX:
change the sticky node:
#extra-wrapper{
position: sticky;
top: 0;
}
aside{
}
#layout{
displ
}
main{
height: 200vh;
}
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
Case 4: display: flexbox/Grid layout - even cols by deafult (Tricky to fix)
You create flex/grid layout & set one of the cols to be sticky. By default the cols height is even = The height of the "nearest ancestor" (wrapper) == Cols height = no scroll effect.
Not working:
#extra-wrapper{
position: sticky;
top: 0;
border: 1px solid red;
}
aside{
}
#layout{
display: flex;
}
main{
height: 200vh;
}
<div id="layout">
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
FIX: Set the sticky aside max-height to be 90vh for example (Now the cols height is not even).
Working:
#extra-wrapper{
position: sticky;
top: 0;
border: 1px solid red;
max-height: 90vh;
}
aside{
}
#layout{
display: flex;
}
main{
height: 200vh;
}
<div id="layout">
<div id="extra-wrapper">
<aside>
<h2>sticky Aside</h2>
</aside>
</div>
<main>
<h1>Article</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui dicta minus molestiae vel beatae natus eveniet ratione temporibus aperiam harum alias officiis assumenda officia quibusdam deleniti eos cupiditate dolore doloribus!
</p>
</main>
</div>
It seems like that the navbar to be stickied shouldn't be inside any div or section with other content. None of the solution were working for me until I took the navbar out of the div which the navbar shared with another topbar .I previously had topbar and navbar wrapped with a common div.
I know this seems to be already answered, but I ran into a specific case, and I feel most answers miss the point.
The overflow:hidden answers cover 90% of the cases. That's more or less the "sticky nav" scenario.
But the sticky behavior is best used within the height of a container. Think of a newsletter form in the right column of your website that scrolls down with the page.
If your sticky element is the only child of the container, the container is the exact same size, and there's no room to scroll.
Your container needs to be the height you expect your element to scroll within. Which in my "right column" scenario is the height of the left column.
The best way to achieve this is to use display:table-cell on the columns. If you can't, and are stuck with float:right and such like I was, you'll have to either guess the left column height of compute it with Javascript.
I know it's too late. But I found a solution even if you are using overflow or display:flex in parent elements sticky will work.
steps:
Create a parent element for the element you want to set sticky (Get sure that the created element is relative to body or to full-width & full-height parent).
Add the following styles to the parent element:
{
position: absolute;
height: 100vmax;
}
For the sticky element, get sure to add z-index that is higher than all elements in the page.
That's it! Now it must work. Regards
1.Position sticky will most probably not work if overflow is set to hidden, scroll, or auto on any of the parents of the element.
2.Position sticky may not work correctly if any parent element has a set height.
Funny moment that wasn't obvious for me: at least in Chrome 70 position: sticky is not applied if you've set it using DevTools.
I know this is an old post. But if there's someone like me that just recently started messing around with position: sticky this can be useful.
In my case i was using position: sticky as a grid-item. It was not working and the problem was an overflow-x: hidden on the html element. As soon as i removed that property it worked fine. Having overflow-x: hidden on the body element seemed to work tho, no idea why yet.
from my comment:
position:sticky needs a coordonate to tel where to stick
nav {
position: sticky;
top: 0;
}
.nav-selections {
text-transform: uppercase;
letter-spacing: 5px;
font: 18px "lato", sans-serif;
display: inline-block;
text-decoration: none;
color: white;
padding: 18px;
float: right;
margin-left: 50px;
transition: 1.5s;
}
.nav-selections:hover {
transition: 1.5s;
color: black;
}
ul {
background-color: #B79b58;
overflow: auto;
}
li {
list-style-type: none;
}
body {
height: 200vh;
}
<nav>
<ul align="left">
<li>Contact</li>
<li>About</li>
<li>Products</li>
<li>Home</li>
</ul>
</nav>
There is polyfill to use for other browsers than FF and Chrome . This is an experimental rules that can be implemented or not at any time through browsers. Chrome add it a couple of years ago and then dropped it, it seems back ... but for how long ?
The closest would be position:relative + coordonates updated while scrolling once reached the sticky point, if you want to turn this into a javascript script
two answer here:
remove overflow property from body tag
set height: 100% to the body to fix the problem with overflow-y: auto
min-height: 100% not-working instead of height: 100%
I believe this article say a lot about how sticky works
How CSS Position Sticky Really Works!
CSS position sticky has two main parts, sticky item & sticky container.
Sticky Item — is the element that we defined with the position: sticky styles. The element will float when the viewport position
matches the position definition, for example: top: 0px .
Sticky Container —is the HTML element which wraps the sticky item. This is the maximum area that the sticky item can float in.
When you define an element with position: sticky you’re automatically
defining the parent element as a sticky container!
z-index is also very important. Sometimes it will work but you just won't see it. Try setting it to some very high number just to be sure. Also don't always put top: 0 but try something higher in case it's hidden somewhere (under a toolbar).
The real behavior of a sticky element is:
First it is relative for a while
then it is fixed for a while
finally, it disappears from the view
A stickily positioned element is treated as relatively positioned until its containing block crosses a specified threshold (such as setting top to value other than auto) within its flow root (or the container it scrolls within), at which point it is treated as "stuck" until meeting the opposite edge of its containing block.
The element is positioned according to the normal flow of the document, and then offset relative to its nearest scrolling ancestor and containing block (nearest block-level ancestor), including table-related elements, based on the values of top, right, bottom, and left. The offset does not affect the position of any other elements.
This value always creates a new stacking context. Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hidden, scroll, auto, or overlay), even if that ancestor isn't the nearest actually scrolling ancestor.
This example will help you understand:
code https://codepen.io/darylljann/pen/PpjwPM
Using the strategy from this blog (https://www.designcise.com/web/tutorial/how-to-fix-issues-with-css-position-sticky-not-working) I came up with an option for those that can't have control over all components in the page
I'm using Angular and in the ngOnInit method I run this code to change the visible propertys of parents to visible
/**
* position: sticky
* only works if all parent components are visibile
*/
let parent = document.querySelector('.sticky').parentElement;
while (parent) {
const hasOverflow = getComputedStyle(parent).overflow;
if (hasOverflow !== 'visible') {
parent.style.overflow = 'visible';
// console.log(hasOverflow, parent);
}
parent = parent.parentElement;
}
Watch out for empty grid areas!
In my case I had something like this:
.cart-areas {
grid-template-columns: 2fr 0.2fr 1.5fr;
grid-template-areas:
'order . summary'
'order . .'
'order . .';
}
I wanted that summary grid item to be sticky when the user completes the checkout form. It didn't work because of those empty grid items (marked with .).
The solution was to delete those empty items like so:
.cart-areas {
grid-template-columns: 2fr 0.2fr 1.5fr;
grid-template-areas:
'order . summary'
'order . summary'
'order . summary';
}
if danday74's fix doesn't work, check that the parent element has a height.
In my case I had two childs, one floating left and one floating right.
I wanted the right floating one to become sticky but had to add a <div style="clear: both;"></div> at the end of the parent, to give it height.
I used a JS solution. It works in Firefox and Chrome. Any problems, let me know.
html
<body>
<header id="header">
<h1>Extra-Long Page Heading That Wraps</h1>
<nav id="nav">
<ul>
<li>Home</li>
<li>Page 2</li>
<li>Page 3</li>
</ul>
</nav>
</header>
<main>
<p><!-- ridiculously long content --></p>
</main>
<footer>
<p>FOOTER CONTENT</p>
</footer>
<script src="navbar.js" type="text/javascript"></script>
</body>
css
nav a {
background: #aaa;
font-size: 1.2rem;
text-decoration: none;
padding: 10px;
}
nav a:hover {
background: #bbb;
}
nav li {
background: #aaa;
padding: 10px 0;
}
nav ul {
background: #aaa;
list-style-type: none;
margin: 0;
padding: 0;
}
#media (min-width: 768px) {
nav ul {
display: flex;
}
}
js
function applyNavbarSticky() {
let header = document.querySelector('body > header:first-child')
let navbar = document.querySelector('nav')
header.style.position = 'sticky'
function setTop() {
let headerHeight = header.clientHeight
let navbarHeight = navbar.clientHeight
let styleTop = navbarHeight - headerHeight
header.style.top = `${styleTop}px`
}
setTop()
window.onresize = function () {
setTop()
}
}
Here's what was tripping ME up... my sticky div was inside another div so that parent div needed some additional content AFTER the sticky div, to make the parent div "tall enough" for the sticky div to "slide over" other content as you scroll down.
So in my case, right after the sticky div, I had to add:
%div{style:"height:600px;"}
(My application has two side-by-side divs, with a "tall" image on the left, and a short data entry form on the right, and I wanted the data entry form to float next to the image as you scroll down, so the form is always on the screen. It would not work until I added the above "extra content" so the sticky div has something to "slide over"
I had the same problem. For me the problem was display: 'none' on big screens (media-query) and display: 'initial' on smartphones. If i removed the display css property and added opacity and pointer events none on desktop everything workedout.
Despite reading this entire page and trying everything but the kitchen sink this simply doesn't work on my mobile device. No sticky action at all -- and that's the only place I need it to work. I have ruled out any special designation or overrides that would stop this from functioning for this screen width. Cleared my mobile formatting code as a test and no change was seen. Works perfectly on Chrome browser on my laptop and not at all on chrome browser on my new S10.
It's TRUE that the overflow needs to be removed or set to initial to make position: sticky works on the child element. I used Material Design in my Angular app and found out that some Material components changed the overflow value. The fix for my scenario is
mat-sidenav-container, mat-sidenav-content {
overflow: initial;
}
One of the most common mistakes when going about the position sticky is:
Browser not supporting it
Not including any top bottom right left properties. Bare in mind that the Browser won't know how to handle that properly if you don't give it enough information. It will be just be statically positioned without it.
I have written about that and more insides on this article. Just putting a reference so I don't repeat myself too much.
I'm trying to use position: sticky on a rotated element but I get extra space at the top. Also where the sticky element has to stops (at the end of parent) it goes outside.
Notice I need to have control to choose how many pixels put between sticky element and the left window side.
Check the 2 screenshot to understand the 2 problems, and what I want to achieve.
Problem 1: extra space at top
Problem 2: sticky element goes outside at the end of section
I'm using this code:
HTML
<section class="section">
<h1 class="section__title">STICKY</h1>
<div class="container">
<div class="row">
<div class="col [ col-lg-8 offset-lg-2 ]">
<div class="h4">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsam quos illum aperiam officia provident, mollitia at, tempore atque, blanditiis sit optio esse harum officiis voluptas iusto sequi. Magni, reiciendis quidem.
</div>
</div>
</div>
</div>
</section>
CSS
.section__title {
display: inline-block;
transform: rotate(-90deg) translatex(-100%);
transform-origin: 0% 0%;
margin-bottom: 0;
position: sticky;
top: 0;
left: 50px;
}
Here is a Codepen with the entire code: https://codepen.io/anon/pen/KLqJGG
How can I solve this?
Thanks
Problem 1: extra space at top
The stickily positioned element stays in the DOM flow - just like a relative positioned element does. So, hence the space there, which is occupied by the h1.section__title element.
Problem 2: sticky element goes outside at the end of section
It is because, the original height of the h1 element is still considered there, even after rotation.
So, you need to determine the exact width of the sticky header (which then becomes the height of this element after rotation) first and then set this width value for the rotated element's height, as follows:
$section-sticky-header-height: 145px;
.section__title {
display: inline-block;
margin-bottom: 0;
transform-origin: 0% 0%;
position: sticky;
top: 0;
left: 50px;
/* solves problem 1 */
float: left;
/* solves problem 2 */
transform: rotate(-90deg) translatex(-$section-sticky-header-height);
height: $section-sticky-header-height;
}
Codepen: https://codepen.io/anon/pen/arybWZ
Edit:
The problem is that I cannot determine the exact width of the sticky header because the h1 text is variable (the client will insert that text via a CMS). Is there a way to handle this? If possible without Javascript
Got it. You can try this instead, if the height is variable:
<h1 class="h1 mb-0 section__title">
<div class="rotation-outer">
<div class="rotation-inner">
<div class="rotation">
STICKY
</div>
</div>
</div>
</h1>
.section__title {
border: 1px solid; // for demo purpose
position: sticky;
top: 0;
float: left;
.rotation-outer {
display: table;
position: relative;
left: 50px;
.rotation-inner {
padding: 50% 0;
height: 0;
.rotation {
transform-origin: 0 0;
transform: rotate(-90deg) translate(-100%);
margin-top: -50%;
white-space: nowrap;
}
}
}
}
See in action: https://codepen.io/anon/pen/BedREm
There's a very good explanation here for how this works: Rotated elements in CSS that affect their parent's height correctly
Edit 2:
At that link I also discovered the writing-mode: vertical-rl; property (in this answer stackoverflow.com/a/50406895/1252920). Do you think could be a better solution? I applied it in this Codepen: codepen.io/anon/pen/JqyJWK?editors=1100 What do you think?
Yes, another sweet alternative, you can use. :)
Here I changed/optimized it a little bit: https://codepen.io/anon/pen/qGXPPe?editors=1100
However, please note that vertical-lr or vertical-rl is not widely supported. Apparently only on desktop version of Chrome/Firefox/Opera. See here.
So, it's up to you, which one to use. Personally, I wouldn't use writing-mode due to lack of browser support.
Seems to me that your Codepen does not have that padding you mention, therefore I wonder if you removed the default padding and margin of html and body elements.
html, body {
margin: 0;
padding: 0;
}
Let me know if it works or in which browser the padding you mention appears.
Let's say I have a couple of columns, of which some I'd like to rotate the values of:
http://jsfiddle.net/MTyFP/1/
<div class="container">
<div class="statusColumn"><span>Normal</span></div>
<div class="statusColumn"><a>Normal</a></div>
<div class="statusColumn"><b>Rotated</b></div>
<div class="statusColumn"><abbr>Normal</abbr></div>
</div>
With this CSS:
.statusColumn b {
writing-mode: tb-rl;
white-space: nowrap;
display: inline-block;
overflow: visible;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
It ends up looking like this:
Is it possible to write any CSS that will cause the rotated element to affect its parent's height, such that the text would not overlap the other elements? Something like this:
Assuming that you want to rotate 90 degrees, this is possible, even for non-text elements - but like many interesting things in CSS, it requires a little cunning. My solution also technically invokes undefined behaviour according to the CSS 2 spec - so while I've tested and confirmed that it works in Chrome, Firefox, Safari, and Edge, I can't promise you that it won't break in a future browser release.
Short answer
Given HTML like this, where you want to rotate .element-to-rotate...
<div id="container">
<something class="element-to-rotate">bla bla bla</something>
</div>
... introduce two wrapper elements around the element that you want to rotate:
<div id="container">
<div class="rotation-wrapper-outer">
<div class="rotation-wrapper-inner">
<something class="element-to-rotate">bla bla bla</something>
</div>
</div>
</div>
... and then use the following CSS, to rotate anti-clockwise (or see the commented out transform for a way to change it into a clockwise rotation):
.rotation-wrapper-outer {
display: table;
}
.rotation-wrapper-inner {
padding: 50% 0;
height: 0;
}
.element-to-rotate {
display: block;
transform-origin: top left;
/* Note: for a CLOCKWISE rotation, use the commented-out
transform instead of this one. */
transform: rotate(-90deg) translate(-100%);
/* transform: rotate(90deg) translate(0, -100%); */
margin-top: -50%;
/* Not vital, but possibly a good idea if the element you're rotating contains
text and you want a single long vertical line of text and the pre-rotation
width of your element is small enough that the text wraps: */
white-space: nowrap;
}
Stack snippet demo
p {
/* Tweak the visuals of the paragraphs for easier visualiation: */
background: pink;
margin: 1px 0;
border: 1px solid black;
}
.rotation-wrapper-outer {
display: table;
}
.rotation-wrapper-inner {
padding: 50% 0;
height: 0;
}
.element-to-rotate {
display: block;
transform-origin: top left;
/* Note: for a CLOCKWISE rotation, use the commented-out
transform instead of this one. */
transform: rotate(-90deg) translate(-100%);
/* transform: rotate(90deg) translate(0, -100%); */
margin-top: -50%;
/* Not vital, but possibly a good idea if the element you're rotating contains
text and you want a single long vertical line of text and the pre-rotation
width of your element is small enough that the text wraps: */
white-space: nowrap;
}
<div id="container">
<p>Some text</p>
<p>More text</p>
<div class="rotation-wrapper-outer">
<div class="rotation-wrapper-inner">
<p class="element-to-rotate">Some rotated text</p>
</div>
</div>
<p>Even more text</p>
<img src="https://i.stack.imgur.com/ih8Fj.png">
<div class="rotation-wrapper-outer">
<div class="rotation-wrapper-inner">
<img class="element-to-rotate" src="https://i.stack.imgur.com/ih8Fj.png">
</div>
</div>
<img src="https://i.stack.imgur.com/ih8Fj.png">
</div>
How does this work?
Confusion in the face of the incantations I've used above is reasonable; there's a lot going on, and the overall strategy is not straightforward and requires some knowledge of CSS trivia to understand. Let's go through it step by step.
The core of the problem we face is that transformations applied to an element using its transform CSS property all happen after layout has taken place. In other words, using transform on an element does not affect the size or position of its parent or of any other elements, at all. There is absolutely no way to change this fact of how transform works. Thus, in order to create the effect of rotating an element and having its parent's height respect the rotation, we need to do the following things:
Somehow construct some other element whose height is equal to the width of the .element-to-rotate
Write our transform on .element-to-rotate such as to overlay it exactly on the element from step 1.
The element from step 1 shall be .rotation-wrapper-outer. But how can we cause its height to be equal to .element-to-rotate's width?
The key component in our strategy here is the padding: 50% 0 on .rotation-wrapper-inner. This exploit's an eccentric detail of the spec for padding: that percentage paddings, even for padding-top and padding-bottom, are always defined as percentages of the width of the element's container. This enables us to perform the following two-step trick:
We set display: table on .rotation-wrapper-outer. This causes it to have shrink-to-fit width, which means that its width will be set based upon the intrinsic width of its contents - that is, based upon the intrinsic width of .element-to-rotate. (On supporting browsers, we could achieve this more cleanly with width: max-content, but as of December 2017, max-content is still not supported in Edge.)
We set the height of .rotation-wrapper-inner to 0, then set its padding to 50% 0 (that is, 50% top and 50% bottom). This causes it to take up vertical space equal to 100% of the width of its parent - which, through the trick in step 1, is equal to the width of .element-to-rotate.
Next, all that remains is to perform the actual rotation and positioning of the child element. Naturally, transform: rotate(-90deg) does the rotation; we use transform-origin: top left; to cause the rotation to happen around the top-left corner of the rotated element, which makes the subsequent translation easier to reason about, since it leaves the rotated element directly above where it would otherwise have been drawn. We can then use translate(-100%) to drag the element downwards by a distance equal to its pre-rotation width.
That still doesn't quite get the positioning right, because we still need to offset for the 50% top padding on .rotation-wrapper-outer. We achieve that by ensuring that .element-to-rotate has display: block (so that margins will work properly on it) and then applying a -50% margin-top - note that percentage margins are also defined relative to the width of the parent element.
And that's it!
Why isn't this fully spec-compliant?
Because of the following note from the definition of percentage paddings and margins in the spec (bolding mine):
The percentage is calculated with respect to the width of the generated box's containing block, even for 'padding-top' and 'padding-bottom'. If the containing block's width depends on this element, then the resulting layout is undefined in CSS 2.1.
Since the entire trick revolved around making the padding of the inner wrapper element be relative to the width of its container which was in turn dependent upon the width of its child, we're hitting this condition and invoking undefined behaviour. It currently works in all 4 major browsers, though - unlike some seemingly spec-compliant tweaks to approach that I've tried, like changing .rotation-wrapper-inner to be a sibling of .element-to-rotate instead of a parent.
As the comment by G-Cyr rightfully points out, current support for writing-mode is more than decent. Combined with a simple rotate, you get the exact result that you want. See the example below.
.statusColumn {
position: relative;
border: 1px solid #ccc;
padding: 2px;
margin: 2px;
width: 200px;
}
.statusColumn i,
.statusColumn b,
.statusColumn em,
.statusColumn strong {
writing-mode: vertical-rl;
transform: rotate(180deg);
white-space: nowrap;
display: inline-block;
overflow: visible;
}
<div class="container">
<div class="statusColumn"><span>Normal</span></div>
<div class="statusColumn"><a>Normal</a></div>
<div class="statusColumn"><b>Rotated</b></div>
<div class="statusColumn"><abbr>Normal</abbr></div>
</div>
Unfortunately (?) this is how it's supposed to work even though you rotate your element it still has certain width and height, that does not change after rotation. You visually change it, but there is no invisible wrapping box that changes its size when you rotate things.
Imagine rotating it less than 90° (e.g. transform: rotate(45deg)): you would have to introduce such invisible box which now has ambiguous dimensions based on the original dimensions of the object you're rotating and the actual rotation value.
Suddenly, you do not only have the width and height of the object you have rotated, but you also have the width and height of the "invisible box" around it. Imagine requesting the outer width of this object - what would it return? The width of the object, or our new box? How would we distinguish between both?
Therefore, there is no CSS that you can write to fix this behavior (or should I say, "automate" it). Of course you can increase the size of your parent container by hand, or write some JavaScript to handle that.
(Just to be clear, you can try using element.getBoundingClientRect to get the rectangle mentioned before).
As described in the spec:
In the HTML namespace, the transform property does not affect the flow of the content surrounding the transformed element.
This means that no changes will be made to the content surrounding the object you're transforming, unless you do them by hand.
The only thing that is taken into account when you transform your object is the overflow area:
(...) the extent of the overflow area takes into account transformed elements. This behavior is similar to what happens when elements are offset via relative positioning.
See this jsfiddle to learn more.
It's actually quite good to compare this situation to an object offset using: position: relative - the surrounding content does not change, even though you're moving your object around (example).
If you want to handle this using JavaScript, have a look at this question.
Use percentages for padding and a pseudo element to push the content. In the JSFiddle I left the pseudo element red to show it and you'll have to compensate the shift of the text but I think that's the way to go.
.statusColumn {
position: relative;
border: 1px solid #ccc;
padding: 2px;
margin: 2px;
width: 200px;
}
.statusColumn i, .statusColumn b, .statusColumn em, .statusColumn strong {
writing-mode: tb-rl;
white-space: nowrap;
display: inline-block;
overflow: visible;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
/* also accepts left, right, top, bottom coordinates; not required, but a good idea for styling */
-webkit-transform-origin: 50% 50%;
-moz-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
-o-transform-origin: 50% 50%;
transform-origin: 50% 50%;
/* Should be unset in IE9+ I think. */
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
}
.statusColumn b:before{ content:''; padding:50% 0; display:block; background:red; position:relative; top:20px
}
<div class="container">
<div class="statusColumn"><span>Normal</span></div>
<div class="statusColumn"><a>Normal</a></div>
<div class="statusColumn"><b>Rotated</b></div>
<div class="statusColumn"><abbr>Normal</abbr></div>
</div>
http://jsfiddle.net/MTyFP/7/
A write-up of this solution can be found here: http://kizu.ru/en/fun/rotated-text/
Please see the updated version in my other answer. This answer is no longer valid and simply does not work any more. I'll leave it here for historical reasons.
This question is quite old, but with the current support for the .getBoundingClientRect() object and its width and height values, in combination with the ability to use this neat method together with transform, I think my solution should be mentioned as well.
See it in action here. (Tested in Chrome 42, FF 33 & 37.)
getBoundingClientRect calculates the actual box width and height of the element. Quite simply, then, we can loop through all elements and set its min-height to the actual box height of their children.
$(".statusColumn").each(function() {
var $this = $(this),
child = $this.children(":first");
$this.css("minHeight", function() {
return child[0].getBoundingClientRect().height;
});
});
(With some modification you can loop through the children and find out which one's the tallest and set the height of the parent to the tallest child. However, I decided to make the example more straightforward. You can also add padding of the parent to the height if you want, or you can use a relevant box-sizing value in CSS.)
Note, though, that I added a translate and transform-origin to your CSS to make the positioning more flexible and accurate.
transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;
The good solution with cross browser support (Firefox, Chrome, Safari, Edge and IE11 too!):
Try it out and resize the browser window in the meantime.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width>">
<title></title>
<style>
.container {
display: flex;
border: 1px solid grey;
}
.vertical,
.horizontal {
padding: 8px;
font-size: 16px;
}
.vertical {
min-width: 20px;
line-height: 20px;
writing-mode: tb-rl;
writing-mode: vertical-rl;
white-space: nowrap;
text-align: center;
transform: rotate(180deg);
}
.horizontal {
border-left: 1px solid grey;
}
</style>
</head>
<body>
<div class="container">
<div class="vertical">
Lorem ipsum dolor sit amet eiusmod
</div>
<div class="horizontal">
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. 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.
</div>
</div>
</body>
</html>
I know it's an old post but I found it while struggling with exactly the same problem. The solution that works for me is rather crude "low-tech" method by simply surround the div I rotate by 90deg with a lot of
<br>
Knowing the approximate width (which becomes height after rotation) of div I can compensate the difference by adding br's around this div so content above and below gets pushed accordingly.
I know there are a few questions about similar topics but they mostly amount to floating the div/image. I need to have the image (and div) positioned absolutely (off to the right) but I simply want the text flow around it. It works if I float the div but then I can't position it where I want. As it is the text just flows behind the picture.
<div class="post">
<div class="picture">
<a href="/user/1" title="View user profile.">
<img src="http://www.neatktp.com/sites/default/files/photos/BlankPortrait.jpg" alt="neatktp's picture" title="neatktp's picture" />
</a>
</div>
<span class='print-link'></span>
<p>BlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlah.</p>
<p>BlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlah.</p>
</div>
Is an example of the HTML
With the CSS being:
.picture img {
background: #fff;
border: 1px #ddd solid;
padding: 2px;
float: right;
}
.post .picture {
display: block;
height: auto;
position: absolute;
right: -10px;
top: -10px;
width: auto;
}
.post {
border: 1px solid #FFF;
border-bottom: 1px solid #e8ebec;
padding: 37px 22px 11px;
position: relative;
z-index: 4;
}
It's a Drupal theme so none of this code is mine, it's just that it's not fully working when it comes to putting a picture there.
I know this is an older question but I came across it looking to do what I believe you were trying to. I've made a solution using the :before CSS selector, so it's not great with ie6-7 but everywhere else you should be good.
Basically, putting my image in a div I can then add a long thing float block before hand to bump it down and the text wraps merrily around it!
img {
float:right;
clear:both;
width: 50% ;
margin: 30px -50px 10px 10px ;
}
.rightimage:before {
content: '' ;
display:block;
float: right;
height: 200px;
}
You can check it out here:
http://codepen.io/atomworks/pen/algcz
Absolute positioning takes the element out of the normal document flow, and therefore it does not interact with the other elements. Perhaps you should revist how to position it using float instead, and ask about it here on Stack Overflow if you get stuck :)
As mentioned by #Kyle Sevenoaks, you are taking absolute positioned content out of the document flow.
As far as I can see, the only way to have the parent div wrap the absolute positioned contents, is to use javascript to set the width and height on each change.
When you position a div absolutely, you're effectively taking it out of the document flow, so the other elements will act as if it's not there.
To get around this, you can instead use margins:
.myDivparent
{
float: left;
background: #f00;
}
.myDivhascontent
{
margin-left: 10px; /*right, bottom, top, whichever you need*/
}
Hopefully that will do the trick :)
In my opinon, the "Absolute" trait is poorly named, because its position is actually relative to the first parent whos position is not static
<div class="floated">
<div style="position: relative;">
<div class="AbsoluteContent">
stuff
</div>
</div>
</div>
I think the best option is to add an additional div after the float content, but still inside the parent to clear previous styles.
<div class="clear"></div>
And CSS:
.clear
{clear:both;}
I needed a similar solution to float a pullout quote (not an image) which would have variable length text inside. The pullout quote needed to be inserted into the HTML at the top (outside the flow of the text) and float down into the content with text that wraps around it. Modifying Leonard's answer above, there is a really simple way to do this!
See Codepen for Working Example: https://codepen.io/chadwickmeyer/pen/gqqqNE
CSS
/* This creates a space to insert the pullout content into the flow of the text that follows */
.pulloutContainer:before {
content: '' ;
display:block;
float: right;
/* The height is essentially a "margin-top" to push the pullout Container down page */
height: 200px;
}
.pulloutContainer q {
float:left;
clear:both;
/* This can be a set width or percent, if you want a pullout that floats left or right or full full width */
width: 30%;
/* Add padding as you see fit */
padding: 50px 20px;
}
HTML
<div id="container">
<div class="pulloutContainer">
<!-- Pullout Container Automatically Adjusts Size -->
<q>Lorem ipsum dolor sit amet, consectetur adipiscing elit Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit Lorem ipsum dolor sit amet.</q>
</div>
<div class="content">
<h1>Sed Aucteor Neque</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in dui mauris. Vivamus hendrerit arcu sed erat molestie vehicula. Sed auctor neque eu tellus rhoncus ut eleifend nibh porttitor. Ut in nulla enim. Phasellus molestie magna non est.</
...INSERT MORE TEXT HERE...
</div>
</div>
Absolute positioning does not let you wrap text. You have to use float and position using margin or padding.
Here's a trick that might work for some:
if you have a container packed with a lot of objects, and you want that positioned object to appear up high in certain cases, and down lower in other cases (various screen sizes for example), then just intersperse copies of the object multiple times in your html, either inline(-block), or with float, and then display:none the items you dont want to see according to the conditions you need.
Here is a JSFiddle to show exactly what I mean: JSFiddle of right positioning high and low
Note: I added color only for effect. Except for the class names, the subject-1 and subject-2 divs are otherwise exact copies of each other.
There is an easy fix to this problem. It's using white-space: nowrap;
<div style="position:relative">
<div style="position: absolute;top: 100%; left:0;">
<div style="white-space:nowrap; width: 100%;">
stuff
</div>
</div>
</div>
For example I was making a dropdown menu for a navigation so the setup I was using is
<ul class="submenu" style="position:absolute; z-index:99;">
<li style="width:100%; display:block;">
Dropdown link here
</li>
<ul>
Image Examples
Without Nowrap enabled
With Nowrap enabled
Also if you still can't figure it out check out the dropdowns on bootstrap templates which you can google. Then find out how they work because they are using position absolute and getting the text to take up 100% width without wrapping the text.