Class not becoming active upon scroll with js - javascript

I'm trying to make it so that when you scroll on my HTML page when you reach a certain section, that respective section should become active,
for example:
<nav class="scrollmenu">
<ul>
<li class="starters">STARTERS</li>
<li class="ramen">RAMEN</li>
</ul>
</nav>
starters should become active when you reach this section on the page:
<section id="starters" class="foodgrid">...</section>
I'm trying to do this with this JS code:
const sections = document.querySelectorAll("section");
const navLi = document.querySelectorAll("nav ul li");
window.addEventListener("scroll", () => {
let current = " ";
sections.forEach((section) => {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
if (scrollY >= sectionTop) {
current = section.getAttribute("id");
}
});
navLi.forEach((li) => {
li.classList.remove("active");
if (li.classList.contains(current)) {
li.classList.add("active");
}
});
});
I'm still going to expand on this JS code to make it work better but if I'm not mistaken it should already make the class visually active when I'm on the right section.
In case you are wondering my CSS looks like this for the time being:
nav ul li:active {
background-color: blue;
}

what happens here is that you are attaching a class name which is active and you are not declaring that class, you just have the event :active that is triggered when you click on the element, so you must do this
.active {
background-color: blue;
}
and that will work when you set the class and remove it, also leaving the css code as you have it will make that when the html li element is clicked it changes it background and then changes to the normal color, try it and let me know if it works, if it doesn't is something about your js and then ill check it

Related

How to make the event listener only add CSS element on one item at a time (vanilla JavaScript)

^I would like to be able for the style to be enabled for only one at a time.
^I'm able to do this, which I don't want the user to be able to do.
So it's weirdly hard framing a question for what is possibly an easy solution. I basically have a list of build versions where I want the user to select one. When one of the versions are selected, it adds a border to the item to display that its clicked. However, with my code right now the user is able to select all 3 items and enable their CSS elements. I would like for the user to be able to only "activate" one item from the list.
HTML and CSS:
<ul class="listContents">
<li><p>Stable</p></li>
<li><p>Preview</p></li>
<li><p>LTS</p></li>
</ul>
<style>
.colorText {
background-color: #58a7ed;
color: white;
}
</style>
and the JS stuff:
const btn = document.querySelectorAll('.links');
for (let i = 0; i < btn.length; i++ ) {
btn[i].addEventListener("click", function() {
btn[i].classList.add('colorText')
})
}
I really hope I made myself clear, I feel like I'm failing my English trying to word this right lol.
You can also use a forEach loop, accessing the clicked link using event.target
const btns = document.querySelectorAll('.links');
btns.forEach(btn => {
btn.addEventListener('click', e => {
// remove any existing active links
btns.forEach(b => b.classList.remove('colorText'));
// activate the clicked link
e.target.classList.add('colorText');
})
});
.colorText {
background-color: #58a7ed;
color: white;
}
<ul class="listContents">
<li>
<p>Stable</p>
</li>
<li>
<p>Preview</p>
</li>
<li>
<p>LTS</p>
</li>
</ul>
Just before you add the colorText class to the desired item, we can remove colorText from ALL of them, ensuring that only 1 at a time gets the class.
// the rest is the same...
btn[i].addEventListener("click", function() {
// remove it from all:
btn.forEach(function(item) {
item.classList.remove('colorText');
});
// add it back to the desired one
btn[i].classList.add('colorText')
})
you can also use simple for of
const btn = document.querySelectorAll(".links");
for (let bt of btn) {
bt.addEventListener("click", (e) => {
btn.forEach((b) => b.classList.remove("colorText"));
e.target.classList.add("colorText");
});
}

How to scroll to a certain section in a page without using href attribute

My navigation bar is self populated with javascript.
function liCreation() {
for(noOfSection of noOfsections) {
sectionAddress = noOfSection.getAttribute('data-nav');
section =noOfSection.getAttribute('id');
liTag = document.createElement('li');
liTag.innerHTML = `<a class='menu__link'>${sectionAddress}</a>`;
}
}
So it comes out with a navigation bar menu that has 4 sections (because I got 4 sections in my page)
I want to scroll to a section without adding href="${section}" to the anchor element.
Why? It gives several benefits. Href shows the arrow pointer and no code is needed to scroll
If you must, then
document.querySelector("ul.nav").addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.tagname === "A" && tgt.classList.contains("menu__link")) {
document.getElementById(tgt.textContent.trim()).scrollIntoView();
}
})
assuming the id is in the text of the anchor and the ul containing the LIs have a class of .nav

Scroll function to navigate to appropriate section using JavaScript

My goal is to complete a dynamic single landing page using JavaScript. HTML and CSS files were already provided and I managed to build an unordered list by manipulating the DOM.
The thing that got me stuck is: When clicking an item from the navigation menu, the link should scroll to the appropriate section.
I cannot get this to work :/
Below is the JS code so far.
/* Declare variables for the fictive document and menu list to retrieve and store variables as unordered list */
const container = document.createDocumentFragment();
const menuList = document.getElementsByTagName('section');
/* Function to create the navigation menu as a clickable link */
function navigationLink(id, name) {
const navLink = `<a class = "menu__link" data-id=${id}">${name}</a>`;
return navLink;
}
/* Function for the navigation list, built as an unordered list */
function createNavigation() {
for (let i = 0; i < menuList.length; i++) {
const newMenuListItem = document.createElement('li');
const menuListName = menuList[i].getAttribute('data-nav')
const menuListID = menuList[i].getAttribute('id')
newMenuListItem.innerHTML = navigationLink(menuListID, menuListName)
container.appendChild(newMenuListItem);
}
/* Retrieve the id from the ul section to be added to the document fragment: container */
const navBarMenu = document.getElementById('navbar__list')
navBarMenu.appendChild(container);
}
// Add class 'active' to section when near the top of viewport
function setActiveClass() {
for (let i = 0; i < menuList.length; i++) {
if (isInViewport(menuList[i])) {
menuList[i].classList.add("your-active-class");
} else {
menuList[i].classList.remove("your-active-class");
}
}
}
To solve the problem I looked into another piece of code that I haven't been able to function properly.
function scrollToElement(event) {
if (event.target.nodeName === 'A') {
const menuListID = event.target.getAttribute('data-id');
const menu = document.getElementById(menuListID);
menu.scrollIntoView({ behavior: "smooth" });
}
}
document.addEventListener('scroll', function () {
setActiveClass();
});
const navBarMenu = document.getElementById('navbar__list')
navBarMenu.addEventListener('click', function (event) {
scrollToElement(event)
})
You can use anchor to do this. This will make your life easier than trying to do this in js.
To use it, you just have to set an id to a node. Like <div id="myFirstId"></div> and then, set the href of your link to #myFirstId.
If you want your scroll to not be instant, you can add the scroll-behavior: smooth to the scrollable element.
html {
scroll-behavior: smooth;
}
#scrollable {
overflow:auto;
}
#firstDiv {
background-color: green;
height: 700px
}
#secondDiv {
background-color: yellow;
height: 200px;
}
#thirdDiv {
background-color: blue;
height: 500px;
}
firstDiv
SecondDiv
thirdDiv
<div id="scrollable">
<div id="firstDiv"></div>
<div id="secondDiv"></div>
<div id="thirdDiv"></div>
</div>
For your code, just change this line <a class = "menu__link" data-id=${id}">${name}</a>
To this <a class="menu__link" href="#${id}">${name}</a>

How to close sidebar on click for anchor using only JavaScript?

I have a sidebar and I want to close it when someone clicks on a link. In my code, the sidebar just closes for a millisecond when I click on an anchor element. How can I fix this without using jQuery?
The a tags are linking to a html page
JS:
var elem = document.getElementById('slidebar').getElementsByClassName('button')[0]
element.addEventListener("click", slide);
function slide() {
document.getElementById('slidebar').classList.toggle('active');
}
var slidebar = document.getElementById('slidebar');
slidebar.addEventListener('click', handleMenuClick);
function handleMenuClick(event) {
if (event.target instanceof HTMLAnchorElement) {
document.getElementById('slidebar').classList.add('close');
}
}
CSS:
#slidebar.active {
left: 0px;
}
#slidebar.close {
display: none;
}
First, make sure you prevent the default event when clicking the anchor tag. Otherwise, it might be re-rendering the page.
But based on your code, it looks like you're adding two functions onto the slidebar. One that closes and one that opens. Since the anchor tag that closes the slidebar is inside the slidebar - when you click it you first fire off the handleMenuClick function and then it bubbles up and fires off the slide function. So it closes and opens quickly.
Instead, add a third element that is used to open the slidebar and attach the slide function there.
Also, you don't need two classes for managing the state of hidden/not hidden. You can just provide a class that sets the display to none and toggle that class list. If you want transition effects you can do that in CSS
Maybe something like this:
window.addEventListener('DOMContentLoaded', e => {
let slidebar = document.getElementById('slidebar')
let collapseButton = slidebar.getElementById('close-button')
let openButton = slidebar.getElementById('open-button')
collapseButton.on('click', toggleClassList)
openButton.on('click', toggleClassList)
const toggleClassList = e => {
e.preventDefault()
slidebar.classList.toggle('hidden')
}
})
#slide-bar.hidden {
display: none;
}
#slide-bar.hidden #close-button {
display: none;
}
#slide-bar #open-button {
display: none;
}
Obviously, it depends a bit on the code you have already written. But this is a basic example that would work. Just need to add the transitions for the sliding effect in CSS

Animating Navigation Menu Dropdowns at the Same Time

I'm attempting to animate my subnav using JavaScript. I want to reveal all subnavs, similar to a 'mega menu' type nav. Right now, it fades in, but from the subnav that's furthest right, causing a gradation effect. I want them all to start at the same time.
How can I make them all animate at the same time?
SeeĀ CodepenĀ for HTML and CSS
Here's the JS block:
nav.forEach(elem => {
elem.addEventListener('mouseenter', () => {
const subnav = document.querySelectorAll('.subnav-block');
subnav.forEach(sub => {
sub.classList.add('display-block');
setTimeout(() => {
sub.style.opacity = 1;
}, 100);
});
});
elem.addEventListener('mouseleave', () => {
const subnav = document.querySelectorAll('.subnav-block');
subnav.forEach(sub => {
sub.classList.remove('display-block');
sub.style.opacity = 0;
});
});
});
There's no problem with your JavaScript. All the subnavs are all animating at the same time: the issue is that they're overlapping. With your CSS as it is right now, your the first subnav is behind the other two, and the second is behind the third, like this:
[ subnav 1 [subnav 2 [subnav 3]]]
You can verify by using your browser's inspect element option.
When the second and third subnavs become transparent, you can see the first subnav behind them, so they appear darker. You need to change your CSS so that they're not overlapping.
Try changing your hover selector as
.nav > li > a:hover {
color: yellow;
}
What this will do is to only apply hover effect on anchor tags which are direct children of li, which are direct children of element having nav class.

Categories