I've noticed that centrally aligning (vertically) an element with position sticky starts to break down after a certain reduction of screen width, depending on that elements width. Unlike position: fixed, the sticky element eventually gets stuck and loses its central alignment.
Any idea why this is happening and a workaround?
div {
height: 100px;
width: 500px;
background-color: red;
position: sticky;
left: 50%;
transform: translateX(-50%);
}
<div>
</div>
I came back to this question after some time. PIzmAR's answer is a good workaround but does not explain why this is happening as fy data suggested and there is a better solution without wrapping HTML elements.
The reason this happens is actually simple. Sticky applies stickiness to both Y and X axes.
Since it also keeps its position/size in the normal DOM flow, its untransformed state will (depending on the sticky elements width) eventually hit the far-right most size of the viewport and create overflow. At this point, sticky elements just behave in the same way they do on the Y axis and STICK in position. That's it! It's the left: 50% attribute that's the culprit, in the same way the top: attribute works when using sticky in a Y direction.
This isn't what I needed at the time and I doubt it's what most people need when using left to centrally align a sticky element. So the best solution is to treat the sticky element as relative and use margins to center it on the X axis. Like so:
#box-2 {
top:0;
left: 0;
transform: translate(0);
margin-left: auto;
margin-right: auto;
}
Run the code snippet in fullscreen to see some examples:
function updateBoxClasses(boxId, highlighted) {
const box = document.getElementById(boxId);
if (highlighted) {
box.classList.add('highlighted');
box.classList.remove('box');
} else {
box.classList.add('box');
box.classList.remove('highlighted');
}
}
function updateClasses() {
const viewportWidth = window.innerWidth;
if (viewportWidth <= 1000) {
updateBoxClasses('box-ghost', true);
updateBoxClasses('box', true);
updateBoxClasses('box-2', true);
} else {
updateBoxClasses('box-ghost', false);
updateBoxClasses('box', false);
updateBoxClasses('box-2', false);
}
}
updateClasses();
window.addEventListener('resize', updateClasses);
* {
box-sizing: border-box;
}
body {
width: 100%;
padding: 0;
margin: 0;
}
div#container {
height: 2000px;
width: 100%;
position: relative;
}
div#heading {
display: flex;
justify-content: center;
align-items: center;
font-size: 64px;
height: 200px;
border: solid black 2px;
}
#box, #box-2 {
position: sticky;
left: 50%;
transform: translateX(-50%);
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
color: white;
opacity: 50%;
top: 100px;
}
#box-2 {
top: 210px;
margin-top: 10px;
left: 0;
transform: translate(0);
margin-left: auto;
margin-right: auto;
}
span {
font-size: 12px;
position: absolute;
top: 5px;
left: 10px;
}
#box-ghost {
position: absolute;
top: 0;
left: 50%;
background-color: transparent;
border-style: dashed;
border-width: 10px;
opacity: 70%;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
}
/* div#half-width {
position: fixed;
left: 0;
right: 0;
width: 250px;
height: 100px;
background-color: grey;
display: block;
color: lightgray;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
} */
.highlighted {
border-color: blue;
color: blue;
background-color: blue;
width: 500px;
height: 100px;
}
.box {
color: red;
background-color: red;
border-color: red;
width: 500px;
height: 100px;
}
<div id="heading"><span>position: relative</span>Resize viewport to see demo</div>
<div id="container">
<div class="box" id="box"><span>position: sticky (left: 50%, transform: translateX(-50%))</span>500px</div>
<div class="box-ghost" id="box-ghost"><span>position: absolute (left: 50%)</span>500px (not transformed)</div>
<div class="box" id="box-2"><span>position: sticky (margin-left: auto, margin-right: auto)</span>500px (solution)</div>
</div>