Sticky header implementation with IntersectionObserver - javascript

I'm doing sticky header implementation - I dont want to use position: sticky; because I dont see good polyfill for it so I'm trying to use IntersectionObserver API instead. The problem is - I dont really like the behaviour it provides as its entries could be emitted with random delay. This solution works fine if you scroll gradually, but if you scroll faster the header goes up and only after delayed response it gets fixed class and gets positioned with significant jump especially on heavy pages. Any ways to achieve better response or maybe IntersectionObserver is not good for my purposes?
const header = document.querySelector('.header');
const headerPositionNotifier = document.createElement('div');
headerPositionNotifier.classList.add('header-position-notifier')
header.parentNode.insertBefore(headerPositionNotifier, header);
const options = {
threshold: [0]
};
const callback = (entries) => {
entries.forEach(({
intersectionRatio
}) => {
if (intersectionRatio == 0) {
header.classList.add('fixed')
} else {
header.classList.remove('fixed')
}
})
}
const io = new IntersectionObserver(callback, options);
io.observe(headerPositionNotifier);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
text-align: center;
font-family: Arial;
color: white;
letter-spacing: 10px;
}
.banner {
background: orange;
padding: 20px;
}
.parent {
background: mediumvioletred;
position: relative;
}
.header {
background: salmon;
padding: 40px 0;
}
.content {
background: #444;
padding: 40px 0;
height: 2000px;
}
.fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
}
.header-position-notifier {
height: 1px;
}
<div class="banner">
BANNER
</div>
<div class="parent">
<div class="header">
HEADER
</div>
<div class="content"></div>
</div>

If you can't use CSS for this, than you out of luck. The delay just comes from the delay from scrolling events which you can't make go away. As you said, on heavy pages it gets worse. Even on different browser it's more or less worse. I would prefer to combine css position sticky and as fallback the intersection observer.

Related

How do I make stacking cards with CSS and JS?

I created the effect I want below:
const container = document.getElementById("container");
const boxes = document.querySelectorAll(".box");
const clearBtn = document.getElementById("clear-button");
const spread = () => {
boxes.forEach((box) => box.classList.remove("selected"));
boxes.forEach((box, i) => {
box.style.left = `${i * 116 + 16}px`;
});
};
const select = (index) => {
spread();
console.log(`selecting ${index}`);
boxes[index].classList.add("selected");
boxes.forEach((box, i) => {
box.style.left = `16px`;
});
};
clearBtn.addEventListener("click", spread, false);
spread();
#container {
display: flex;
}
.box {
position: absolute;
font-size: 64px;
display: flex;
align-items: center;
justify-content: center;
height: 150px;
width: 100px;
top: 5vh;
z-index: 1;
transition: left 1s;
cursor: pointer;
}
.selected {
z-index: 2;
}
.one {
background-color: cyan;
}
.two {
background-color: magenta;
}
.three {
background-color: yellow;
}
button {
position: absolute;
outline: none;
border: none;
background-color: green;
color: white;
padding: 8px;
border-radius: 4px;
top: 185px;
left: 16px;
}
<div id="container">
<div onclick="select(0)" class="box one">1</div>
<div onclick="select(1)" class="box two">2</div>
<div onclick="select(2)" class="box three">3</div>
</div>
<button id="clear-button">Spread</button>
However, the only way I've been able to manage it is with absolute positioning, which I need to avoid so that this component can be manipulated elsewhere on the DOM.
The idea is that I have a collection of "cards." When one is clicked, all of them slide all the way to the left, leaving the selected card on top. Also, it would be ideal to have a way to reverse the process as well.
I wish very badly that I could do this with CSS grid, and it may be possible with Firefox, but that is currently the only browser that supports animating grid-template-columns.
Ultimately this will need to be done in React, but I am happy to see any answers using any other framework! If there are any libraries that might accomplish this, that would be great too, but again, this will be in React at the end of the day, so that may limit what's available.
This is the only way I've found to accomplish this.
Note: This question is asking a very similar question, but without the movement, and the answer seems to be absolute positioning anyway.

Navbar change Color using Observer

Hey out there reading,
i'm making a webpage and im relativly new to JavaScript. I want the Navbar to change Color when its in section Two, while in Section One and Three the Navbar should have the same Color. I watched some tutorials and was able with that code to change the color of the navbar so that in section one and section two the navbar has the right color. When trying out the variables sectionTwo and sectionThree (in order to get the navbars Color to switch back to the color in sectionOne when entering sectionThree) on the other hand it didnt change the colors at the right position (like 100px befor the section). I dont know why this problem accures. If someone knows how to fix it, it would mean the world to me :).
const header = document.querySelector("header")
const sectionOne = document.querySelector(".one")
const sectionTwo = document.querySelector(".two")
const sectionThree = document.querySelector(".three")
const sectionOneOptions = {
}
const sectionOneObserver = new IntersectionObserver(function(entries, sectionOneObserver) {
entries.forEach(entry => {
if (entry.isIntersecting) {
header.classList.add("nav-scrolled")
} else {
header.classList.remove("nav-scrolled")
}
});
},
sectionOneOptions);
sectionOneObserver.observe(sectionTwo)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Arial";
}
body {
width: 100vw;
height: 100vh;
margin: 0;
background: #000;
}
header {
--text: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 8rem;
z-index: 10000;
padding: 0 1rem 5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
header nav {
margin: auto;
display: flex;
justify-content: center;
align-items: center;
}
header nav li {
flex: 0 0 auto;
list-style: none;
margin-left: 1rem;
}
header nav li a {
text-decoration: none;
padding: 6px, 15px;
color: var(--text);
border-radius: 20px;
}
.nav-scrolled {
--text: #000;
}
.one {
position: relative;
width: 100%;
height: 100vh;
padding: 200px 20vw;
display: flex;
}
.two {
height: 100vh;
position: relative;
padding: 100px 20vw;
background: #fff;
}
.three {
position: relative;
padding: 100px 20vw;
color: #fff;
height: 100vh;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<header>
<nav>
<li>one</li>
<li>two</li>
<li>three</li>
</nav>
</header>
<section>
<div class="one" id="one"></div>
</section>
<section>
<div class="two" id="two"></div>
</section>
<section>
<div class="three" id="three"></div>
</section>
<script src="app.js"></script>
</body>
</html>
Let me know if you need some more Code and not only the snippet to fix this.
Hopefully some hero can help me :).
Greetings
Noel
I did research to try and answer this question, so please take the preface that I am not an expert in IntersectionObserver.
With that preface out of the way,
There were many things I changed from your example to create the desired effect, however the core feature that you would have needed to implement in your example is the option for intersection observers, rootMargin. By adding this option you can give the item on the screen a negative top and bottom margin. This is important, because you do not want the observer to fire until the header is about to cross into the observed section, and you dont want the header to change until just before it crosses back into the next section.
The way I chose to emulate this behavior, is by using the rootMargin option to give a negative margin. This makes the actual element observed begin however many pixels after we specify. For example, an element that is 100px in height, with "0px 0px -20px 0px" as the value for rootMargin in the options object, would not trigger the IntersectionObserver until 20px of the element was scrolled into the viewport height.
With this understanding, we can define our goal. We want to preform an action when the observed element is about to touch the header. We can describe this as the viewport height minus the header height in a negative margin to the bottom would adjust the element just enough to trigger the intersection observer as desired. Because the methods of obtaining margins are not exact, I subtract one from the numbers calculated to adjust for small error. If we set both margins to overlap eachother, we will never have the observer fire.
I also decided to use css variables and set the value of the variable depending on whether or not the element observed scrolled into or out of the viewport.
This code is not able to execute properly in a stack snippet because the viewport option that the intersection observer defaults to in the options is not set correctly for the snippet environment. I have not tested this code in environments that resize.
Hopefully that explanation helps you understand this code. Let me know if you have any questions.
relevant html:
<header>
<nav>
<li>one</li>
<li>two</li>
<li>three</li>
</nav>
</header>
<section id="one">
</section>
<section id="two">
</section>
<section id="three">
</section>
<script src="app.js"></script>
relevant css:
:root {
--header-text-color: white;
}
body {
margin: 0;
}
header {
position: fixed;
top: 0;
width: 100%;
}
header nav {
display: flex;
justify-content: center;
align-items: center;
}
header li {
list-style: none;
padding: 1rem;
font-size: 2rem;
}
header a {
color: var(--header-text-color);
text-decoration: none;
}
section {
height: 120vh;
background: black;
}
#two {
background: white;
}
relevant js:
const header = document.querySelector("header");
const sectionTwo = document.querySelector("#two");
const topMargin = header.offsetHeight - 1;
const bottomMargin = window.innerHeight - header.offsetHeight - 1;
const options = {
rootMargin: `-${topMargin}px 0px -${bottomMargin}px 0px`,
}
const observer = new IntersectionObserver(([entry]) => {
const color = entry.isIntersecting ? "black" : "white";
document.documentElement.style.setProperty('--header-text-color', color);
}, options);
observer.observe(sectionTwo);

How to put an element in hover state when scrolled down using javascript

I am trying to make a div element which when scrolled down will change properties drastically. Here is the codepen example of how I want it to work.
Instead of hover I want it so that when scrolled down, the page wide div will turn into that little circle div which when clicked will function as a back to the top button. It doesn't matter if more classes are added or anything of that sort. I am very new to js and I tried a few things and also googled about it, I got the scroll code from w3school's how to make a back to top button guide which specifies that when scrolled down by 20px the code would react, but I don't know how to turn the JavaScript to JS when scrolled down along with the transformation of the div.
Thanks in advance
I think you want to implement scroll to top functionality, very common these days in most of the web app.
You need to keep below things and design that feature.
There is one header, that should have a reference ID with hash to scroll back to top
Create a button that will always static position (JS) button, will show up when user scroll the window
Bind click event on the button that scroll back to top
Here is the you can see this implementation and use it.
.html
<h1 class="intro-copy">
Scroll down to use this simple back-to-top button made with modern vanilla javascript.
</h1>
<a class="top-link hide" href="" id="js-top">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6"><path d="M12 6H0l6-6z"/></svg>
<span class="screen-reader-text">Back to top</span>
</a>
.css
body {
height: 2000px;
position: relative;
}
.intro-copy {
padding: 1em;
margin: 50vh auto;
max-width: 15em;
font-family: Helvetica;
font-weight: lighter;
font-size: 2em;
line-height: 1.2;
text-align: center;
}
.top-link {
transition: all .25s ease-in-out;
position: fixed;
bottom: 0;
right: 0;
display: inline-flex;
cursor: pointer;
align-items: center;
justify-content: center;
margin: 0 3em 3em 0;
border-radius: 50%;
padding: .25em;
width: 80px;
height: 80px;
background-color: #F8F8F8;
&.show {
visibility: visible;
opacity: 1;
}
&.hide {
visibility: hidden;
opacity: 0;
}
svg {
fill: #000;
width: 24px;
height: 12px;
}
&:hover {
background-color: #E8E8E8;
svg {
fill: #000000;
}
}
}
// Text meant only for screen readers.
.screen-reader-text {
position: absolute;
clip-path: inset(50%);
margin: -1px;
border: 0;
padding: 0;
width: 1px;
height: 1px;
overflow: hidden;
word-wrap: normal !important;
clip: rect(1px, 1px, 1px, 1px);
&:focus {
display: block;
top: 5px;
left: 5px;
z-index: 100000; // Above WP toolbar
clip-path: none;
background-color: #eee;
padding: 15px 23px 14px;
width: auto;
height: auto;
text-decoration: none;
line-height: normal;
color: #444;
font-size: 1em;
clip: auto !important;
}
}
JS:
// Set a variable for our button element.
const scrollToTopButton = document.getElementById('js-top');
// Let's set up a function that shows our scroll-to-top button if we scroll beyond the height of the initial window.
const scrollFunc = () => {
// Get the current scroll value
let y = window.scrollY;
// If the scroll value is greater than the window height, let's add a class to the scroll-to-top button to show it!
if (y > 0) {
scrollToTopButton.className = "top-link show";
} else {
scrollToTopButton.className = "top-link hide";
}
};
window.addEventListener("scroll", scrollFunc);
const scrollToTop = () => {
// Let's set a variable for the number of pixels we are from the top of the document.
const c = document.documentElement.scrollTop || document.body.scrollTop;
// If that number is greater than 0, we'll scroll back to 0, or the top of the document.
// We'll also animate that scroll with requestAnimationFrame:
// https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
if (c > 0) {
window.requestAnimationFrame(scrollToTop);
// ScrollTo takes an x and a y coordinate.
// Increase the '10' value to get a smoother/slower scroll!
window.scrollTo(0, c - c / 10);
}
};
// When the button is clicked, run our ScrolltoTop function above!
scrollToTopButton.onclick = function(e) {
e.preventDefault();
scrollToTop();
}

Adding non-centered text shifts centered elements [duplicate]

This question already has answers here:
How to prevent scrollbar from repositioning web page?
(26 answers)
Closed 4 years ago.
After putting a centered header, I add a non-centered output with JS. After the output is produced, the header shifts a bit left. What can be done to tackle this problem?
let output = [];
function spit() {
for (let i = 0; i < 100; i++) {
output.push(i);
}
document.getElementById("output").innerHTML =
output.join("<br>");
}
.header {
background-color: lightgray;
border: none;
color: black;
padding: 17px 25px;
display: block;
text-align: center;
font-size: 36px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
<h2 id="dictName" class="header">
Testing Page
</h2>
<button style="font-size:20pt;height:35pt" onclick="spit()">
Press me!
</button>
<p id="output">
</p>
One crazy solution might be to set you body height to the view port height, that way you start off with a scroll, avoiding the shift when the button gets pressed.
let output = [];
function spit() {
for (let i = 0; i < 100; i++) {
output.push(i);
}
document.getElementById("output").innerHTML =
output.join("<br>");
}
body {
min-height: 100vh;
}
.header {
background-color: lightgray;
border: none;
color: black;
padding: 17px 25px;
display: block;
text-align: center;
font-size: 36px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
<h2 id="dictName" class="header">
Testing Page
</h2>
<button style="font-size:20pt;height:35pt" onclick="spit()">
Press me!
</button>
<p id="output">
</p>
I added a universal { margin:0; padding:0;} to your css code. The code did seem to be centered but I think the margin of -50 (that's being created by the auto margin ) is throwing off the look.
let output = [];
function spit() {
for (let i = 0; i < 100; i++) {
output.push(i);
}
document.getElementById("output").innerHTML =
output.join("<br>");
}
* {
margin: 0px; padding:0px;
}
button {
/*margin-left:15px;*/
margin-top:7px;
font-size: 20pt;
height: 35pt;
}
.header {
background-color: lightgray;
/* border: 15px solid white;*/ /*use the commented props if you still want the "indented" effect" */
color: black;
padding: 17px 25px;
display: block;
text-align: center;
font-size: 36px;
width: 100%;
margin-right:auto;
margin-left:auto;
}
<h2 id="dictName" class="header">
Testing Page
</h2>
<button onclick="spit()">
Press me!
</button>
<p id="output">
</p>
IF you don't want the h1 to shift due to the scrollbar, you would have to calculate, using css calc() (and maybe some other things too), 50vw - (widthOfH1/2). This works because the vw unit (viewport width) is not affected by the scrollbar.
One way for the scrollbar to not affect the centering of the h1 would be to use JQuery.
$(#dictName).style.marginLeft = 'calc(50vw -'+(this.width/2)+'px)';
I haven't tested this so I'm not 100% sure if it will work, but please tell me if it does or doesn't. You may need to rerun this code when the button is pressed.
After some googling it seems I found the easiest way to solve this problem here, on SO:
How to prevent scrollbar from repositioning web page?
html {
overflow-y: scroll;
}
Probably, the question should be closed as duplicate, but I don't have enough reputation to do it.

Floating menu, how to stick menu to the top?

I'm relatively new to js and fighting with floating menu.
This is how my js code look like
$(function(){
console.log('jest');
$(window).scroll(function(event){
console.log($('#menu').offset().top, $(this).scrollTop());
if ($('#menu').offset().top <= $(this).scrollTop()+$(window).height()) {
$('#menu').addClass("fixed");
} else {
$('#menu').removeClass("fixed");
}
});
});
When the top is achieved, fixed class is added properly.
My question is:
What should I do inside fixed class to make the menu stick to the top?
Nice an simple "position: sticky"..
brand {
display: block;
background-color: pink;
padding: 10px;
font-size: 20pt;
}
header {
background-color: yellow;
border: 2px solid black;
padding: 10px;
position: sticky;
top: 0;
}
section {
background-color: silver;
padding: 10px;
}
body {
padding: 0;
margin: 0;
}
<brand>
<div>This is our branding,. It can scroll away.</div>
<small>for all your header needs,.. </small>
</brand>
<header>
This is the header
</header>
<section>
Our othe stuff can go in here.<br><br><br><br><br><br><br><br><br><br>
Scroll down<br><br><br><br><br><br>
Even further<br><br><br><br><br>
A little bit more<br><br><br><br><br>
Ok I'm bored now.
</section>
<header>
This is repeated, see how the header takes over
</header>
<section>
Our othe stuff can go in here.<br><br><br><br><br><br><br><br><br><br>
Scroll down<br><br><br><br><br><br>
Even further<br><br><br><br><br>
A little bit more<br><br><br><br><br>
Ok I'm bored now.
</section>
.fixed{
position: fixed;
top: 0;
right: 0;
left: 0;
}

Categories

Resources