Navbar change Color using Observer - javascript

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);

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.

offsetting browser native smooth scroll via anchor links

My document is using browser-native smooth scroll behavior defined via CSS (and not javascript) and anchor links to scroll down to a specified part of the page when clicked on.
This works fine except that the top of the page the anchors are located at end up hidden underneath the sticky nav bar that is fixed to the top of the screen after the animated scroll has finished. I therefore need to offset the scroll's destination Y coordinate.
Example (when you click):
#media ( prefers-reduced-motion: no-preference ) {
html {
scroll-behavior: smooth;
}
}
nav {
position: fixed;
top: 0;
background-color: rgba(204, 204, 204, 0.9);
padding: 1rem;
text-align: center;
width: 100%;
}
body {
margin: 0;
}
main {
margin: 4rem 1rem 120vh;
}
<nav>Nav Bar</nav>
<main>
Example Anchor Link
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<p id="example">Scroll Target</p>
</main>
Is there a way to offset the scroll target so that it's not covered up by the nav bar? I don't think there is a javascript event that fires when the browser's scroll animation has completed. I also haven't seen a way to customize the browser's animation duration.
I just stumbled upon this question...
The answer is pretty simple, you can always give some padding top to the target element, in this case for instance 60px of padding 👇 since the nav bar is 50px in height. This will act as an offset.
#media ( prefers-reduced-motion: no-preference ) {
html {
scroll-behavior: smooth;
}
}
nav {
position: fixed;
top: 0;
background-color: rgba(204, 204, 204, 0.9);
padding: 1rem;
text-align: center;
width: 100%;
}
body {
margin: 0;
}
main {
margin: 4rem 1rem 120vh;
}
<nav>Nav Bar</nav>
<main>
Example Anchor Link
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<p id="example" style="padding-top: 60px;">Scroll Target</p>
</main>

Change the content of a H1 tag while scrolling in another section - Using IntersectionObserver?

I have 4 sections on my website each section has a fullscreen height in css of min-height: 100vh and each section has a unique color.
Created a fixed block which is set to the bottom with a width of 100%. Inside this fixed div I have a section title.
Now, I am trying to change this section title text based on the current in viewport DIV. I want to give each section a unique section title to let visitors know which section is currently visible.
But I noticed scrolling down with the Oberserver works fine but scrolling up works only If you reach the top of the first section.
My CSS:
section {
min-height: 100vh;
position: relative;
display: flex;
align-items: center;
justify-items: center;
}
My HTML:
Fixed div:
// The Section title should be dynamic based on the data attribute: `data-section-title`
<div class="fixed-bar"><h2 class="section--title"><!-- innerHTML --></div>
<section id="intro" data-section-title="Intro">Content</section>
<section id="about" data-section-title="About">Content</section>
<section id="services" data-section-title="Services">Content</section>
<section id="contact" data-section-title="Contact">Content</section>
Is there a clean and nice way to solve this?
This snippet sets the IntersectionObserver to trigger if just over half of a section is visible. When it does, the title is copied to the fixed div.
The IntersectionObserver doesn't have to do anything when a section's visibility drops below the threshold.
let options = {
threshold: 0.51
}
let callback = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
document.querySelector('.section--title').innerHTML = entry.target.getAttribute('data-section-title');
}
});
};
let observer = new IntersectionObserver(callback, options);
const sections = document.querySelectorAll('[data-section-title]');
for (let i = 0; i< sections.length; i++) {
observer.observe(sections[i]);
}
body {
width: 100vw;
height: 100vh;
}
section {
min-height: 100vh;
position: relative;
display: flex;
align-items: center;
justify-items: center;
}
#intro { background: cyan; }
#about { background: magenta; }
#services { background: yellow; }
#contact { background: lime; }
.fixed-bar {
top: 100%;
transform: translateY(-100%);
position: fixed;
width: 100%;
height: 4em;
background: #eeeeee;
z-index: 1;
}
<section id="intro" data-section-title="Intro">Content</section>
<section id="about" data-section-title="About">Content</section>
<section id="services" data-section-title="Services">Content</section>
<section id="contact" data-section-title="Contact">Content</section>
<div class="fixed-bar"><h2 class="section--title"><!-- innerHTML --></h2></div>
This takes the element from the center of the screen using the document.elementFromPoint method.
window.onscroll = function(e) {
this.oldScroll = this.scrollY;
var elementAtPos = document.elementFromPoint(window.innerWidth/2,window.innerHeight/2);
if(elementAtPos) {
document.querySelector("h2.section--title").innerText = elementAtPos.getAttribute("data-section-title");
}
}
section {
min-height: 100vh;
position: relative;
display: flex;
align-items: center;
justify-items: center;
border: 2px solid red;
}
.fixed-bar {
position: fixed;
}
<div class="fixed-bar"><h2 class="section--title">Content</h2></div>
<section id="intro" data-section-title="Intro">Content</section>
<section id="about" data-section-title="About">Content</section>
<section id="services" data-section-title="Services">Content</section>
<section id="contact" data-section-title="Contact">Content</section>
You could change where the element is taken from.
There might be issues with overlays and older browsers.
document.getElementById('yourid').addEventListener('scroll', function() {
//Your code to change the h1 tag
})

How to set an element so that it uses the remaining height of the parent element whilist having other 2 dynamic elements on it?

So let me elaborate a little bit more in the title, I have this fixed container that has 3 elements inside of it, all 3 of them should change dynamically since the first and the second one have preference over the third one I need this one to take the rest of the containers space.
I've tried using flexbox but I doesn't seems to have a solution for this particular problem I have, (Or maybe I just don't know how to use it)
And I also tried using JS to get the h1 and p height and then substract it from the container with this little function
document.getElementById('DescTitle').clientHeight;
but I can't make it work...
<div class="Container" id="DescContainer">
<h1 id="DescTitle">Title</h1>
<p id="DescParagraph">A longer text</p>
<div Class="Interior-Container">
</div>
</div>
body {
margin: 0;
text-align: center;
}
.Container {
height: 100vh;
color: black;
}
.Container h1 {
font-size: 8vh;
margin-left: 3vw;
margin-right: 3vw;
}
.Container p {
font-size: 4vh;
margin-left: 3vw;
margin-right: 3vw;
}
.Interior-Container {
background-color: black; /*I'm only using this for testing porpuses*/
}
So pretty much I just want "Interior-Container" to take the rest of the view height that the title and paragraph left and for this solution to work even if you change the window size, cause I'm planning on putting even more dynamic objects based of the size of this last div...
You can achieve this with flex, but therefor you need to change your CSS a bit.
You need to set diplay: flex;and flex-direction: column; at .Container class.
And for the elements inside your container you can use min-height if you want to make all elements the same height if the content fits. For the third element in your container you set flex: 1 to make it flexible.
https://www.w3.org/TR/css-flexbox-1/#flex-common
This CSS should do the job if I read your question correctly:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
text-align: center;
}
.Container {
height: 100vh;
color: black;
display: flex;
flex-direction: column;
}
.Container h1 {
background-color: blue;
min-height: 33%;
}
.Container p {
background-color: orange;
min-height: 33%;
}
.Interior-Container {
flex: 1;
background-color: black;
}
<div class="Container" id="DescContainer">
<h1 id="DescTitle">
Title<br />
Title
</h1>
<p id="DescParagraph">
A longer text<br />
A longer text<br />
A longer text<br />
A longer text<br />
A longer text<br />
</p>
<div Class="Interior-Container">
</div>
</div>

How to keep my custom range/slider follower div perfectly centered with range thumb in Vanilla JavaScript

I've spent some time working on a slider that has a div follow the thumb of slider. I did this by creating a relatively positioned container for the follower (called #slider-follower-cntnr). I then adjust the position of the follower div (#slider-follower) according to the value of the range input. The problem is that as you move the thumb across the track the follower does not stay perfectly centered against the position of the thumb. I've tried adjusting the width of the #slider-follower-cntnr but I don't no how to find the correct width to keep it perfectly centered. Thanks for any help.
TL/DR: How can I keep follower div centered perfectly to the thumb of the range input across all range values?
Heres a codepen. You may need to full screen to see it get off center as you pull it along the range.
HTML
<div id="slider-cntnr">
<input type="range" id="frame-slider" oninput="updateFollowerValue(this.value)" />
<div id="slider-follow-cntnr">
<div id="slider-follow">
<div id="slider-val-cntnr">
<span id="slider-val"></span>
</div>
</div>
</div>
</div>
JS
var follower = document.getElementById('slider-follow');
var follower_val = document.getElementById('slider-val');
var slider = document.getElementById('frame-slider');
var updateFollowerValue = function(val) {
follower_val.innerHTML = val;
follower.style.left = (val*1) + '%';
};
updateFollowerValue(slider.value);
CSS
html, body {
width: 100%;
height: 100%;
}
#slider-cntnr {
width: 80%;
margin: 40px;
position: relative;
display: flex;
flex-direction: column;
background: orange;
}
#frame-slider {
width: 100%;
margin: 0;
}
#slider-follow-cntnr {
position: relative;
background-color: blue;
height: 10px;
margin: 0 auto;
width: 98%;
}
#slider-follow {
background-color: black;
width: 30px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
margin-left: -15px;
}
#slider-val-cntnr {
background-color: white;
width: 25px;
height: 20px;
}
#slider-val {
margin-left: 9px;
}
Few ways you can fix this:
(1) In the HTML
<div id="slider-val-cntnr">
<center>
<span id="slider-val"></span>
</center>
</div>
(2) In the CSS
#slider-val {
margin-left: auto;
margin-right: auto;
}
(1) The HTML method probably works the best, but it's generally not best practice to have <center> tags throughout your HTML.
(2) This will make sure that the number never goes beyond the bounds of the white box that it resides in. If you don't care that it is absolutely centered, and just want the number to not exit the box, then this is a suitable solution.

Categories

Resources