Position: sticky - scroll bouncing when combined with javascript height adjustment - javascript

After playing with position: sticky for a while, I started implementing it for sticky navigation and ran into this interesting, but frustrating scroll bouncing issue.
This is a common type of navigation behaviour seen on many sites, where you would traditionally use javascript to calculate offsets to a relative element in the page. When the element reaches the top of the window, a 'stuck' class would be added, taking the element out of the document flow with position: fixed, and a dummy element of the same height would be added in it's place to prevent the page from 'jumping'. Additionally, it's common to see javascript then shrink the height of that navigation to save space while scrolling.
CSS now seemingly takes care of all this with position: sticky, apart from (as far as I can tell), detecting when the element is 'stuck'. Instead I used some javascript to do the stuck detection, discovering that everything works great, right up until the height of the sticky element needs to change.
It's pretty hard to explain, but it wreaks havoc in production - so here's a stripped down example I've made to illustrate the issue as simply as possible.
CSS sticky position height adjustment bug
It's best illustrated when the height of the page is just the right length, so I've set a fixed height on the element to make sure everyone is able to see the same thing. You can add more content and it's still an issue when scrolling past.
The result is some really weird behaviour. When scrolling down, the navigation sticks, and as it shrinks the navbar, the 'dummy element' the browser is automatically creating courtesy of position: sticky seems to be kept in sync with it. That means, when the stuck class is added, the whole page gets smaller, and a fraction of a second later, the navigation is no longer stuck, thus resulting in a glitchy vibration loop.
The behaviour is also completely different across every browser I've tested. In chrome, this bouncing can never be resolved, it stays in the infinite loop constantly adding / removing the stuck class. More interestingly in Safari, the scroll position is 'pushed back' to a state where it wont bug out. Then in Firefox, it does both of these, glitching for a second or two, before forcing the scroll position back up again.
I'm wondering if anyone has experienced this, and come up with any solutions? Any js workaround I've come up with hasn't really worked or been very good! Surely as popularity grows, more people are going to hit this one...
Genius workarounds, hacks, insights, or perfect solutions all welcome!

Try adding overflow-anchor: none; to the sticky element when applying changes that would alter its size (and potentially affect window size/element positioning).
Update: ultimately, the right solution I've hit on is: have an outer element that NEVER changes size (it's always the same full height at any given breakpoint). That one is made sticky. But it also should have no background/visual styles, and it's effective height should be defined by height + bottom margin (so that it takes up the right amount of initial space in the document, but doesn't actually block clicks once the visual nav shrinks and gives more space.
Then have an inner element that does change size, either in reality or just visually.
You can also use modern properties like contain: layout size; on the inner element like

(Apparently you need more reputation to comment than answer ...)
This seems like a legitimate layout bug, so I'm curious what the opinion of browser contributors might be. Raised issues in the Chromium and Firefox bug trackers to see what'll happen:
https://bugs.chromium.org/p/chromium/issues/detail?id=734461
https://bugzilla.mozilla.org/show_bug.cgi?id=1374171

I can confirm this is a problem after attempting the same thing. I'm using position sticky on my header and adding a class at the same time via JS (to trigger some animations which change height as the CodePen's above describe)
var header = document.getElementById("header");
var sticky = header.offsetTop;
window.onscroll = function () {
if (window.scrollY > sticky) {
header.classList.add("stuck");
} else {
header.classList.remove("stuck");
}
};
The height change does in fact mess with the window height and as it becomes 1px smaller will trigger the else which removes my animation. Removing the animation changes the height back to the original size and the loop starts again.
I'd like to know how to code this correctly without a native stuck element/class/pseudo

I forked your pen.
Here is one workaround I came up with that visually gives the same effect.
It appears that transitioning a transform instead of height along with position: sticky works just fine. You don't get the constant class toggling.
So if we want to halve the height of our nav, we can squish it in half by changing scaleY from 1 to 0.5
This in turns squishes our links, so we then scale those up to double their original size to offset the squishing, adjusting scaleY from 1 to 2.
The last fix we have to do is translating the nav up to the top of the page to compensate for the smaller height.
Snippet is below. The key parts here are as follows:
nav {
transform: scaleY(1) translateY(0);
}
nav a {
transform: scaleY(1);
}
nav.stuck {
transform: scaleY(0.5) translateY(-50%);
}
nav.stuck a {
transform: scaleY(2);
}
nav, nav a {
transition: all 0.6 ease-in-out;
}
The first two rules are not strictly necessary, but I like to include a before and after just to make things extra clear.
nav = document.querySelector('nav');
section = document.querySelector('section');
function supportSticky() {
if(window.CSS && CSS.supports) {
return CSS.supports("(position: sticky)") || CSS.supports("(position: -webkit-sticky)");
} else {
var el = document.createElement("div");
el.style.position = "sticky";
return el.style.position == "sticky";
}
}
function handleScroll() {
function isStuck(el) {
return el.offsetTop - section.scrollTop <= 0 ? true : false;
}
isStuck(nav) ? nav.classList.add("stuck") : nav.classList.remove("stuck");
}
if (supportSticky()) section.addEventListener('scroll', handleScroll);
html,
body,
h1 {
margin: 0;
font-family: arial;
}
section {
width: 100%;
max-width: 600px;
margin: 0px auto;
box-shadow: 0 1px 7px #ccc;
height: 378px;
overflow-y: scroll;
}
header {
padding: 3em;
}
nav {
display: flex;
width: 100%;
background-color: #ddd;
justify-content: center;
padding: 3em;
box-sizing: border-box;
position: sticky;
top: 0;
transition: all .6s ease-in-out;
transform: scaleY(1) translateY(0);
}
nav.stuck {
background-color: red;
transform: scaleY(0.5) translateY(-50%);
}
nav.stuck a {
transform: scaleY(2);
}
nav a {
text-decoration: none;
color: #fff;
padding: 1ch 1em;
background-color: #bbb;
margin-right: 1em;
border-radius: 3px;
transition: all .6s ease-in-out;
}
nav a:hover {
background-color: #aaa;
}
article {
padding: 3em;
}
<section>
<header>
<h1>CSS sticky position height adjustment bug</h1>
</header>
<nav>
Item 1
Item 2
Item 3
Item 4
</nav>
<article>
<h1>Sticky navigation</h1>
<p>The navigation above should shrink when it gets to the top.</p>
<h1>There is no 'stuck feature' in CSS</h1>
<p>So we need javascript to work that out, and set a stuck class.</p>
<h1>But it bounces!</h1>
<p>Because the dummy element is kept in sync with the nav height...</p>
</article>
</section>

This was driving me mad for a while, but (based on solution from This question/answer) a good solution is to create an additional external element that is the sticky one which never changes size, and then have the internal element change size/position within that container as needed.
An example fiddle I made with an observer to detect when 'stuck' (could also use scroll offset if there's just a fixed-size element above it):
https://jsfiddle.net/ccarnage/fveyc6nL/24/
Summary is:
<div id="sticky-container" style="height:100px;">
<div id="header-contents-shrinkable">
...
</div>
</div>
Where #header-contents-shrinkable will have its style changed when the sticky-container is stuck to the top of the page (e.g. height reduced)

Related

div element fixed relative to window but limited to its parent

I am trying to position a box in a fixed way relative to the window, but only within the limits of a certain section of the site. You can visualise this easier in the jsfiddle that I created.
https://jsfiddle.net/animyrch/dzrpg6gh/
My element (the yellow box) needs to remain where it is on the screen. And it does exactly that. But the wrapper element (blue box) has a height of 20em. And when the screen is scrolled further than that, the fixed element continues to stay on the screen and goes out of its wrapper. How can I ensure that it doesn't go further than a specific point (for example, after 19 em or the starting point of the padding or the border of the wrapper, etc.) and starts to disappear from the screen as the scrolling goes on?
I have searched for an answer but all my search queries are understood as "keeping an element fixed relative to its parent". It's true that when the fixed element reaches a certain point, I want it to be no longer fixed relative to the window but relative its parent but it's clear that it's not the same thing.
Thank you in advance for your answers.
Here is the code from jsfiddle:
HTML
<div class="wrapper">
<div class="fixedElement">
<p>This should stay fixed on screen but shouldn't go lower than its parent</p>
</div>
</div>
<div class="enlargingPage"></div>
CSS
.wrapper {
background-color: blue;
padding: 2em;
left: 50%;
top: 50%;
height: 20em;
}
.fixedElement {
background-color: yellow;
position: fixed;
}
.enlargingPage {
background-color: gray;
height:30em;
}
What you are probably looking for is position: sticky. Check the MDN documentation on how to use it.
https://developer.mozilla.org/en-US/docs/Web/CSS/position
Remember browsers natively don't support it much. http://caniuse.com/#feat=css-sticky
You can obviously polyfill it using one of the polyfills out there. Example - https://github.com/wilddeer/stickyfill

Set div to cover all window size, minus menu bar height, without scrollbars

I'm trying to build a slider plugin from scratch because I need it to have some specific behavior that is hard to find in other plugins. Anyway, one of the requirements is that it takes all the space available in the page, minus the menu bar height.
This is an example I set up: https://jsfiddle.net/5ujqw94n/
To set the size of the size I call this function:
adjustSizes: function(that) {
var w = document.body.parentNode.clientWidth;
var h = $(window).height() - $('header').height();
console.log('window inner w', $(window).innerWidth());
that.$elem.width(w);
that.$elem.height(h);
console.log('window inner w', $(window).innerWidth());
console.log('document client w', document.body.parentNode.clientWidth);
}
I also call the function when resizing the window. What happens is that when the page is loaded, it shows the scrollbars, but resizing the window makes them disappear.
In the function I put some console.log to track the window width and noticed that there's a 15px difference after the resizing is applied. I thought this could have had something to do with the scrollbars and searching arouns seems this is the case.
I'm not sure how to solve it. Putting overflow: hidden kind of works, but I was hoping for another approach because this feels like a workaround.
For example, in this other page everything seems to work as I want to and there's no overflow: hidden in the body.
Any suggestion?
I found a solution that's (kind of) working at least in chromium.
Remove borders from .page-container and modify .slider-container
.slider-container {
border: 0px solid yellow;
left: 0;
right: 0;
height: 100%;
max-width: 960px;
margin: auto;
overflow: hidden;
position: absolute;
}
Now pour some body { margin: 0; } on top of it and serve refreshed.
What baffles me however is why the borders are causing the horizontal scroll bars even with box-sizing: border-box; stated... hope it helps anyways.

Dynamically Update Width of Float Left Div

I've looked through a variety of other posts, and to no avail, I have yet to find the kind of solution I'm looking for. Many solutions involved people using CSS with methods like fixing the right and left (which wouldn't make it worth floating and a waste of brain power) or to word-wrap at a certain amount to the right (which also defeats the purpose of what I'm trying to do.
My problem exists like this:
I have 3 divs: wrapper, menu, and content. Wrapper is used to apply a background to 100% of the page, and create extra styling properties to be inherited by other CSS. Menu is for my menu script I coded in JQuery and it takes up 400px of space on the left hand of the screen and descends downwards at 100vh. I may change it to fixed, but it doesn't change the issue. Anyway, content is the rest of the page; let's say the other 80% of it. I have both menu and content floating left and it works just fine. However, until text wraps at the end of the screen, the div goes under (disappears in my case) the screen and no longer viewable.
My solution:
function simplyWidth(changed, menu1, wrapper){
var wrapperWidth = $(wrapper).width();
var menuWidth = $(menu1).width();
var newWidth = wrapperWidth - menuWidth;
$(changed).css("width", newWidth);
};
Does it work? Of course it works. The only problem is, it isn't dynamic at all! It resizes to the screen once, and you have to refresh the page just to get it to update again. Is there a way to take that JQuery/Javascript and make it so I can just update it every .1 of a second? Would that make the page lag? Or am I doing it wrong.
Also assume that my HTML is spot on, and it needs no corrections. The reason I won't disclose it is because there's too much there for me to post and to not confuse the living crap out of you people.
This is the basic layout of my page:
<div id="wrapper">
<div id="menu1"></div>
<div id="content"></div>
</div>
As for my wrapper CSS:
#wrapper {
width: 100%;
background-color:black;
margin: 0 auto;
top: 0px;
overflow:hidden;
height: 100vh;
background-image:url(Assets/background1.jpg);
}
Menu1 CSS:
#menu1 {
margin: 0 auto;
height: 100%;
width: 400px;
background-color:#191919;
color:white;
z-index: 400;
float: left;
}
Content CSS:
#content {
float:left;
color:white;
height: 100vh;
}
listen to resize event and call the same function when the window is resized:
$(window).resize(simplyWidth);

Centering Two Dynamic-Width Divs On The Same Line and

Okay I apologize if this is a repeat - but I couldn't find any working answers anywhere.
I want to have two divs (50% width each) side by side... so a left and a right - inside of a content div (see photo below).
I want them to have min-widths at 300px and once the page gets smaller than 600px (which is when both divs will reach their mins) I want to divs to wrap.. so one on top the other.
I've tried to do that here: fiddle but am having problems.
Here is EXACTLY what I want:
You're making things hard for yourself! This can be done quickly and easily with inline-blocks. Have a nice JSfiddle.
Lets explain the code:
.wrapper
{
text-align: center; /* specifies that the inline-blocks (which are treated
like text here) will be centered. */
font-size: 0; /* Explained later */
max-width: 1000px; /* Your desired max-width */
position: relative; /* These two lines center your wrapper in the page. */
margin: 0 auto;
}
Now for the inside 50% elements:
.left, .right{
display: inline-block; /* This will treat these divs like a text element.
This will work with the parent's "text-align: center" to center the element. */
min-width: 300px;
width: 50%;
font-size: 16px; /* Explained below */
vertical-align: text-top; /* Explained below */
}
You might be wondering why font-size is included. It is because with this method comes a little quirk - if a font size is kept at default, the div's will have an annoying gap between them that can not be eliminated with margin.
However, adding font-size: 0; to the parent element eliminates this gap. It's weird, and you then have to specify the font-size for your children elements, but it's well worth it for the ease of use.
But there's still a problem - the blue element is pushed down, and isn't flush on the top. This can be remedied with vertical-align: text-top; This will make sure all Div elements are aligned by the tops, so they lay in a more pleasant pattern. This is just another little quirk to remember when using inline-blocks. I know it seems like a lot of things to fix just for something this simple, but compared to your other options using inline-block is the cleanest and easiest way of going about this. (Though if you prefer, jshanley offers a very good alternative using float elements)
Also, because these children are now treated like text, they will automatically reposition themselves when the window gets too small! No media-queries needed. Yay.
Good luck.
Instead of using inline-block which causes some sizing quirks, you can use block elements, and float both .left and .right to the left, giving each a width of 50%.
Then to make them stack you need to do a little calculating. Since you specified that the wrapper is 80% of the page width, and the break point for the content is at 600px (each element 300px) the page's breakpoint would be at 750px since 80% of 750 is 600.
You can make a media query that will only apply styles when the page width is less than 750px and set .left and .right to width 100% to make them stack.
#media only screen and (max-width: 750px) {
.left, .right {
width: 100%;
}
}
It's very simple to implement, and gives a good result, here's the fiddle.
I think both #jshanley and #emn178's answers do the trick, but I want to point something out:
The display: inline-block; css property doesn't work with float: right nor float: left, since when you use the float property, it ALWAYS automatically set the display property to block.
Since you're doing this:
.right{
min-width:100px;
background-color:purple;
height:100%;
margin-left:50%;
display:inline-block;
}
The display: inline-block; property is doing nothing.
left and right could have same layout, so I add a class block.
To use float:left and width:50%, it should work.
http://jsfiddle.net/emn178/mzbku/7/
I add media query, it should be what you want.
But you need to calculate how to set the size.

DIV height 100% for remaining space under another div

So I have a header bar for a page I made with a height of 150px. Under that area I want another DIV to fill the remaining space (width and height) all across the screen and to the bottom of the screen. I've tried setting height: 100% for the DIV, but that causes the screen to become scrollable and I only want it to fill the remainder of the page. NOTE: There is NO footer or anything under it.
Using jQuery/Javascript is acceptable, but CSS-only is prefered (if possible). If using jQuery, please explain the proper way to have it implemented into the page (I'm assuming $(function() {...}); under the <style> tag in the head.
I've tried searching for a result before, but nothing seems to work correctly.
tl;dr I basically made 3 options for you. click on the 'like this' in the below paragraph to see what they all look like without any text. Click on the A). B). and C). links in the paragraphs below that to see the difference between the three options. Check how each one scrolls differently, they are all different I promise. After you look at all three you can read how the one you want is implemented. (that is if you like any of them.) Hope you like it, no problem if you don't :)
I'll have a go at this, because it honestly depends on what you're going after there are multiple ways to look at it and it depends on your end goal. I will cover three possible scenarios: (which all look the same without text mind you, like this, but if you want to see what they look like with text click the letters. Make sure you scroll the page to see the difference between them.)
(Just as a side note I based A). and B). off how Twitter Bootstrap does it.)
A). You just want it to look like one div on top of the other (header div on top of main-content div) and display like that, but you still want the page to scroll if the 2nd div's text overflows. In this implementation when they scroll will move the header out of view, but if you don't want the header div to move out of view that leads me to
B). Same as the first header div on top of main-content div, but when they scroll the header div will still stay in place at the top instead of moving out of view.
and last of all,
C). You really do want the div to stretch to the bottom of the screen and never have the scroll bar for the whole page. This could be used in some cases, for instance, Spotify makes a nice music app with this kind of style so that you never scroll the whole page just panes in the page.
Ok so first here is the html code used to construct all three of them
<body>
<div class="header"></div>
<div class="main-content"></div>
</body>
And now to the fun part...
I will provide a Fiddle for the following examples, and with the css I will put the necessary code at the top and the unneccessary code at the bottom. (The html may have some unneccasary text so just ignore that. I just want you to see the page scrolls differently on the three.)
A).
no need to rephrase what it is so I'll just show you the code that is necessary.
First, here is A). without the text just so you can see what it looks like the others until the content gets too large.
Here is the fiddle with the text so you can see how it differs.
Here is the necessary css for A). (the background-color isn't completely necessary, but it is somewhat necessary to show the point.)
body {
padding-top: 150px;
background-color: #ddd;
}
.header {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 150px;
background-color: #676767;
}
and now for...
B).
First, here is B). without the text just so you can see what it looks like the others until the content gets too large.
Here is the fiddle with the text so you can see how it differs.
Here is the necessary css for B).
body {
padding-top: 150px;
background-color: #ddd;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 150px;
background-color: #676767;
}
As you can probably tell the only difference is the position: fixed on the .header, but look at the two examples to see the difference it makes.
and now last of all C).,
C).
First, here is C). without the text just so you can see what it looks like the others until the content gets too large.
Here is the fiddle with the text so you can see how it differs, and with I'll call option 1 where it has a scroll bar just for that area's overflowing content.
Here is the fiddle with the text so you can see how it differs, and with I'll call option 2 where it hides the overflowing content. (This is honestly bad practice and I wouldn't do it. So if I may suggest. I would go with option 1 of C).)
Here is the necessary css for C).
body {
padding-top: 150px;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 150px;
background-color: #676767;
}
.main-content {
position: fixed;
top: 150px;
left: 0;
right: 0;
bottom: 0;
background-color: #ddd;
}
I won't explain it, but here is an article on positioning that will.
here is the only necessary css for option 1 is adding overflow-y: auto to .main-content, but if you want to go with option 2 which I don't suggest you can go with overflow-y: hidden on .main-content
Well that's all for my post which is probably too long for most people sorry if I bored you, but I'm just trying to help. Hope you figure out the layout you want. This is only a few examples of the layouts possible with good old css. If you don't get the layout you want from this or any other post feel free to send me a message by commenting below, and I'll be happy to answer it sometime. Hope this helped. If it didn't that's fine too. :)
You can try css3 flexbox.
http://jsfiddle.net/wL9aM/1/
.container {
display: -webkit-flex;
display: flex;
flex-direction: column;
height: 700px;
}
.header {
height: 200px;
background: red;
}
.main {
-webkit-flex: 1;
flex: 1;
background: blue;
}
try using script..
var window_h = $(window).height();
var header_h = $("header").height(); //This is what you said you had 150px
$(".filler_div").height(window_h - header_h);
You can also put that inside a function() so that you can add it also when you resize the browser, the filler space also adjusts...
function setfillerDivHeight(){
//the code above
}
$(document).ready(function(){
setFillerDivHeight(); //the initial setting of height
});
$(window).resize(function(){
setFillerDivHeight(); //reapply setting of height when window resizes
});
<div class="full-page-height-wrapper">
<header></header>
<main></main>
</div>
html,body {
height: 100%;
margin: 0;
}
header {
height: 150px;
}
.full-page-height-wrapper {
min-height: 100%;
position: relative;
}
CODE: http://fiddle.jshell.net/N7zJg/9/
preview: http://fiddle.jshell.net/N7zJg/9/show/
I don't think you cannot acheive that in pure CSS.
So, there is two different solutions:
1) You can put the 150px div in the 100% div.
2) You can do it with jQuery:
If your top div is <div id="A"> and the second one is <div id="B">, you'll have:
var b = $("#B");
var height = $("body").height() - b.position().top;
b.css({ height: height });
Feel free to adapt the code if you have some margins.
Found a solution myself finally. Doing it this way makes the design more responsive since (if i choose to add something to the bottom), it will automatically resize the div's height.
.container {
display: table;
}
.row {
display: table-row;
}
.column {
display: table-column;
}
#fullDiv {
height: 100%;
}
I found two solution.
The one is that I have must set the div in the absolute position.
the div float over the screen.
another one is use table-row display.
If you use just CSS, you cant achieve your task by giving 100% height to div. Because what basically CSS is doing is giving 100% height to your DIV plus giving 150 px to above header. Consider giving height of DIV less than 100% or some static value such as 600px or 700px.
Alternate is having a class of DIV with min-height 100% and inside it putting your header and body.

Categories

Resources