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)
I have a div, on which I am applying css3 transform to make it look 3d & these transforms change as per the mousewheel events.
First look at the div (the brown board with dots) in normal state:
Now I apply this small css code to transform it!
.board-class{
transform-style: preserve-3d;
transform-origin: center top;
transform: translateY(0) rotateX(30deg);
}
you can guess what this code will do, right? But it does not work in expected way, this is how it renders on chrome:
But on Firefox this work well without issue:
Here is link to hosted site : http://www.buildactivityboard.com/how-it-works
Can anyone guide me what I am doing wrong, this seems like a silly issue but I can't find out what I am doing wrong.
Note:
Believe me, this used to work without issue on Chrome too! I don't know what happened now to cause this problem. I've checked this on Mac & Windows, behaviour remains same!
Changing the position from static to absolute fixes the issue:
.master-board .widget-board {
width: 750px;
height: 300px;
padding: 0;
position: absolute;
left: 50vw;
top: 50vh;
margin: -96px 0 0 -375px;
z-index: 1000;
transition: all 1.5s;
}
https://bugs.chromium.org/p/chromium/issues/detail?id=20574
Either adding perspective: 1000px to body or .master-board works.
EDIT: According to this post, seems like it's caused by a conflict between 3d transforms and position:fixed. The chromium bug tracker marked this issue as "wont fix".
I've created a fiddle to reproduce this issue:
https://jsfiddle.net/uuhqsw57/
Adding perspective to its nearest parent helped solve the issue.
http://staging.isaidicanshout.com/test3d/
I am using CSS3 rotate3D to create a 3D grid of tiles that respond to mouse movement. In safari everything works fine, but in Chrome, the draw order is determined by the order of the code, not the position in z-space. If an element passes in front of an element that should appear behind it, but appears after it in the code, it will be obscured.
Is there any way to fix this?
From what I understood your concern is you can use z-index css property on your .tile elements and give them position: relative; for the z-index to work properly:
.tile {
float: left;
pointer-events: auto;
position: relative;
}
On <div> tags assign z-indez value as desired:
<div style="z-index: 4">
See working jsfiddle here:
http://jsfiddle.net/jh5etqfk/
Notice img/02.jpg is on top of img/01.jpg and img/03.jpg
NOTE: Images URL are referencing to your site.
Try using this the preserve-3d and perspective css values:
.wrap {
text-align: center;
transform-style: preserve-3d;
perspective: 1000px;
}
See this jsFiddle
I am confused by the size of the <html> reported by Chrome browser.
I am working in full-screen with screen resolution 1360x768. I use this css to put the full screen image on the background:
bg-img { position:absolute; top:0; left:0; width:1360px; height:768px; }
Unfortunately this doesn't show up the image on full screen, and is smaller. I go for the inspect element on <html> markup and see size like 1790 x 768. Computed size reported by Chrome:
display: block;
height: 768px;
width: 1790.6666259765625px;
What I found this works OK in --chrome-frame mode (width: 1360px, but have another problem with extensions so cant use that)
What is going on?
The solution with % even if works OK for background image, doesn't solve my problem because I have other absolute elements on this background and I can't position them with %, because it is not enough precise.
I though that maybe reported 1790.6666259765625px is caused by my extended desktop and 2'nd monitor, but after disabling the same problem.
Instead
bg-img { position:absolute; top:0; left:0; width:1360px; height:768px; }
Try to do this:
bg-img { position:absolute; top:0; left:0; width:100%; height:100%; }
It always works to me and will work in different monitors.
Good luck!
You want CSS3 background-size which allows you to adapt the image ratio regardless of the screen resolution. Make sure you match the CSS selector and adjust the URL to the image you are using...
.bg-img
{
background-image: url(wallpaper.jpg);
background-size: 100% auto;
}
For a live demonstration first pick a wallpaper at my site...
http://www.jabcreations.com/forums/?prompt=themes-wallpaper
...and then change the "wallpaper Effect" here...
http://www.jabcreations.com/forums/?prompt=options-basic
You'll find the relevant CSS code at themes/style_user.css once it's applied.
I wrote a small script to let a label move out of the way everytime the corresponding input field is needed.
Please check it out here: http://jsfiddle.net/5nZWJ/68/
The problem is: it works just as expected in Firefox, but all other browsers I tried (Chromium, Internet Explorer and others) don't keep the bottom-border justified (hard to explain but you will see it if you try it out).
What do I have to change to make this thing in all browsers look like in Firefox?
Thank you in advance!
I have solved your problem. It is now smooth in all browsers: http://jsfiddle.net/5nZWJ/70/
The key is having #formWrapper positioned absolutely from the bottom. This means when the height is increased it expands from the bottom up and doesn't need to recalculate the position from the top.
CSS:
#wrapper {
background-color: lightblue;
height: 110px;
width: 500px;
position:relative; /* Allows absolute figures to be predictable */
}
#formWrapper {
background-color: yellow;
border-bottom: 4px solid red;
bottom: 29px; /* Changed from top and new measurement added */
left: 120px;
height: 57px;
position: absolute;
z-index: 1;
width: 108px;
}
JavaScript:
I removed all lines of code referring to the position, as it no longer needs to be changed or recalculated.
I think this might be related how different browsers count border pixels
http://ejohn.org/blog/sub-pixel-problems-in-css/
(not actually the same problem, but you get some idea)
Instead of using border, I recommend you add a div wrapper around the element, with the background color set to border color and padding set to the border width.