Ok! First I'd like to apologize as this question wasn't possible to be answered without rendering the HTML. Fortunately, I have found the solution.
TL;DR In this case, no, you need JavaScript. You will need to implement a translateY
transform in the element to achieve this. I don't know if the problem is that the parent element has a transform property and it causes this bug or there's something else causing the issue.
Explanation:
I'm currently using a carousel JS library called tiny slider. I'm displaying form elements instead of images, (Building a responsive table; Had issues when I tried using CSS Grids). So far, so good. The problem started when I wanted to set sticky
the date headers.
I went with the modern approach of setting position:sticky
, but that didn't work. The elements would get clogged in a certain position and it wouldn't move or stick. I started researching online (which ended up asking this same question), and the HTML itself. I did find that there were many parent <div>
s that were created by tiny-slider. My theory was that it was getting attached to one of those parents.
Therefore, I decided to try the old tactic of combining position:fixed
with a scroll
event. But, that didn't work. Going back online and Google-Fuing a bit, there seems to be an old bug [1] [2] [3] that whenever a translate is applied to one of the parents an out-of-root container is created and position:fixed
doesn't work as expected.
I have a hunch that this may be one of the reasons why sticky didn't work, but according to this answer, it doesn't seem like it.
I kept thinking for a while, and resorted to use a transform
CSS property with translateY
. I made a small experiment in the browser, and it worked!
Hence, I ended up implementing the scroll
eventListener and listening to the header's parent's position, and applying getBoundingClientRect() to get the offset. If I had applied it to the element itself, it would have given me the translated position which I applied through CSS.
I was skeptical that this could be a performance bottleneck for mobile browsers. Therefore, I checked that the transform function was called inside a requestAnimationFrame
and it had applied a will-change
property in the CSS stylesheet.
I ran the code with a 4x CPU Slowdown in Google Chrome, and had good results .
Here's the resulting function I have (Where elemsToFixed are all the <header>
elements, and threshold is the top offset so it doesn't conflict with the navbar):
export function fixedHeaderScroll(elemsToFixed: HTMLHeadingElement[], threshold: number) {
if (!elemsToFixed || elemsToFixed.length === 0) {
console.error("elemsToFixed can't be null or empty");
return;
}
console.log('Total elems', elemsToFixed.length);
// We assume that all of the elements are on the same height.
const firstEl = elemsToFixed[0];
let propSet = false;
window.addEventListener('scroll', (e) => {
window.requestAnimationFrame(() => {
const top = firstEl.parentElement!.getBoundingClientRect().top;
if (top > threshold) {
if (!propSet) return;
propSet = false;
setElemsFixed(elemsToFixed, top, threshold, false);
return;
}
propSet = true;
setElemsFixed(elemsToFixed, top, threshold);
});
});
}
function setElemsFixed(elemsToFixed: HTMLHeadingElement[], top: number,
threshold: number, setFixed = true) {
console.log('SetElemsFixed is', setFixed);
elemsToFixed.forEach((elem) => {
if (!setFixed) {
elem.removeAttribute('style');
return;
}
elem.style.transform = `translateY(${(top * -1)}px)`;
});
}
The following picture shows a 4x slowdown in the CPU and the calculation of the style (With 26 elements) is about 29.4ms (Which is great!). Using Chrome 70 on Windows and i7 4700MQ processor.
