Make navbar link active on wheel event rather than scroll? - javascript

I want to make the links in my navbar have an active class when you scroll into the corresponding section.
The code below was working just fine until I implemented a smooth scroll/parallax library which removes the scroll event.
I tried making the code work using the wheel event, tried seeing if there was anything similar to scrollY for wheel events but I couldn't find anything.
Any ideas on how I could implement this feature? It can be different from the implementation I had before
EDIT: Here's a codepen. If you uncomment the locomotive portion, the code no longer works. How can I make it work?
const sections = document.querySelectorAll("section");
const navLinks = document.querySelectorAll("nav a");
window.addEventListener("scroll", function () {
let navbar = document.querySelector("nav");
let current = "";
sections.forEach(function (section) {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
if (scrollY >= sectionTop - sectionHeight / 3) {
current = `#${section.getAttribute("id")}`;
}
navLinks.forEach(function (each) {
// add/remove active class
each.classList.remove("nav-active");
if (each.getAttribute("href") == current) {
each.classList.add("nav-active");
}
});
});
});

Try using an IntersectionObserver instead:
const navLinks = document.querySelectorAll("nav a");
const updateNav = (entries, observer) => {
const matchingIds = entries.filter(e => e.isIntersecting).map(e => `#${e.target.id}`);
if (matchingIds.length !== 0) {
const current = matchingIds[0];
navLinks.forEach(function(link) {
link.classList.remove("nav-active");
if (link.getAttribute("href") == current) {
link.classList.add("nav-active");
}
});
}
};
const options = {
root: null,
rootMargin: "0px",
threshold: 0.66
}
const observer = new IntersectionObserver(updateNav, options);
document.querySelectorAll("*[data-scroll-section]").forEach(el => observer.observe(el));
Intersection Observer API
Demo

Related

Adding class to element after scroll

So I'm working on showing/hiding a nav element based on scrolling behavior. Once the user scrolls and scrolls past the nav element, I add a class to make it sticky but keep it out of view. Then once the user stops scrolling I add another class to the transition the element into view. Once the user scrolls again that class needs to be removed again and the nav disappears again.
This is the JS
let mobile_toolbar = document.querySelector(".mobile-toolbar");
let mobile_toolbar_top = (mobile_toolbar.offsetTop) + 50;
let scrollpos = window.scrollY;
let timer = null;
window.addEventListener(
"scroll",
function () {
scrollpos = window.scrollY;
console.log(timer)
if (timer !== null) {
if (scrollpos > mobile_toolbar_top) {
mobile_toolbar.classList.add("mobile-toolbar__hidden");
mobile_toolbar.classList.remove("mobile-toolbar--fixed");
clearTimeout(timer);
} else {
mobile_toolbar.classList.remove("mobile-toolbar__hidden");
mobile_toolbar.classList.remove("mobile-toolbar--fixed");
clearTimeout(timer);
}
}
if (scrollpos > mobile_toolbar_top) {
timer = setTimeout(function () {
mobile_toolbar.classList.add("mobile-toolbar--fixed");
}, 400);
}
},
false
);
As you can see I'm setting a timer to detect when the user stops scrolling and also check the scroll position to determine whether the add the fixed class or not. However, this isn't quite working as I'd like as the nav once slides down as soon as I scroll past itself and then disappears again as the timer is already not null at this point. Can anyone tell me what's wrong with my cod or if there's a better way to detect when the user has stopped scrolling? Vanilla JS only please as I'm trying not to use jQuery
you can refer a below code (it's tell you when user stop scrolling)
<html>
<body onscroll="bodyScroll();">
<script language="javascript">
var scrollTimer = -1;
function bodyScroll() {
document.body.style.backgroundColor = "white";
if (scrollTimer != -1)
clearTimeout(scrollTimer);
scrollTimer = window.setTimeout("scrollFinished()", 500);
}
function scrollFinished() {
document.body.style.backgroundColor = "red";
}
</script>
<div style="height:2000px;">
Scroll the page down. The page will turn red when the scrolling has finished.
</div>
</body>
</html>
Check out this example:
https://codepen.io/len0xx/pen/JjadOgR
const toolbar = document.querySelector('.mobile-toolbar')
const mobileToolbarTop = (toolbar.offsetTop) + 50
let previousScroll = 0
let previousTimeout = 0
window.addEventListener('scroll', () => {
const currentScroll = window.scrollY
if (currentScroll > mobileToolbarTop) {
if (currentScroll > previousScroll) {
toolbar.classList.add('mobile-toolbar__hidden')
toolbar.classList.remove('mobile-toolbar--fixed')
}
else {
toolbar.classList.remove('mobile-toolbar__hidden')
toolbar.classList.add('mobile-toolbar--fixed')
}
}
if (previousTimeout) {
clearTimeout(previousTimeout)
}
previousTimeout = setTimeout(() => {
const newScroll = window.scrollY
if (newScroll <= currentScroll) {
toolbar.classList.remove('mobile-toolbar__hidden')
toolbar.classList.add('mobile-toolbar--fixed')
}
}, 300)
previousScroll = currentScroll
})

Add javascript script only when class is on hover

I have this javascript that outputs an action to the elements on class="link_mainhub"
<script>
const el = document.querySelector('.link_mainhub')
const fx = new TextScramble(el)
let counter = 0
const next = () => {
fx.setText(phrases[counter]).then(() => {
setTimeout(next, 800)
})
counter = (counter + 1) % phrases.length
}
next()
</script>
However, I just want to run the script whenever class="link_mainhub" is hovered.
I tried changing the const el = document.querySelector('.link_mainhub') to const el = document.querySelector('.link_mainhub:hover') but it didn't work.
How can I reproduce the script only when my class is being hovered?
Thanks.
Why not add eventListener of Mouseover to the element.
el.addEventListener('mouseOver', next)

Horizontal Scrolling on React Component Using Vertical Mouse Wheel

I have a component that resizes into a horizontal row of bootstrap cards when in a smaller desktop window. For users without a horizontal mouse wheel and not using a touchpad, I would like to allow users to scroll horizontally using their vertical mouse wheel movements when hovering over this particular component.
Here is the original StackOverflow issue I based my code off of:
https://stackoverflow.com/a/15343916/8387497
Horizontal Scroll helper component:
function horizontalScroll (event) {
const delta = Math.max(-1, Math.min(1, (event.nativeEvent.wheelDelta || -event.nativeEvent.detail)))
event.currentTarget.scrollLeft -= (delta * 10)
event.preventDefault
}
How I've implemented it on component requiring horizontal scrolling:
<Row className='announcements-home' onWheel={horizontalScroll} >
When I've placed this horizontalScroll helper function within the React onWheel event, it scrolls horizontally AND vertically. My desired outcome is just horizontal scrolling. Also, Firefox does not appear to respond at all to horizontal scrolling with these changes.
Okay, so the issue seems to be that you only refer to the function event.preventDefault rather than invoking it.
Adding some brackets at the end to invoke it should do the trick:
event.preventDefault().
I however found this issue while looking for some simple code to use, so I will also leave the hook I made for this if others in the same situation:
import { useRef, useEffect } from "react";
export function useHorizontalScroll() {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = e => {
if (e.deltaY == 0) return;
e.preventDefault();
el.scrollTo({
left: el.scrollLeft + e.deltaY,
behavior: "smooth"
});
};
el.addEventListener("wheel", onWheel);
return () => el.removeEventListener("wheel", onWheel);
}
}, []);
return elRef;
}
Usage:
import React from "react";
import { useSideScroll } from "./useSideScroll";
export const SideScrollTest = () => {
const scrollRef = useHorizontalScroll();
return (
<div ref={scrollRef} style={{ width: 300, overflow: "auto" }}>
<div style={{ whiteSpace: "nowrap" }}>
I will definitely overflow due to the small width of my parent container
</div>
</div>
);
};
Note:
The scroll behavior "smooth" seems to be giving some trouble when trying to do continuous scrolling. This behavior can be omitted to have proper continuous scrolling, but it will look jerky.
As far as I know, there is no easy solution for this. I have however created a rather involved solution in my own project, so thought some people may appreciate that also: https://gist.github.com/TarVK/4cc89772e606e57f268d479605d7aded
onWheel = (e) => {
e.preventDefault()
var container = document.getElementById('container')
var containerScrollPosition = document.getElementById('container').scrollLeft
container.scrollTo({
top: 0,
left: largeContainerScrollPosition + e.deltaY
behaviour: 'smooth' //if you want smooth scrolling
})
}
There is another small problem with TarVK's proposed hook. Once you scroll to the end and continue scrolling nothing happens, when we are used to containing elements starting to scroll as well. So I made a fix for that:
export function useHorizontalScroll () {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = (e) => {
if (e.deltaY === 0) return;
if (
!(el.scrollLeft === 0 && e.deltaY < 0) &&
!(el.scrollWidth - el.clientWidth - Math.round(el.scrollLeft) === 0 &&
e.deltaY > 0)
) {
e.preventDefault();
}
el.scrollTo({
left: el.scrollLeft + e.deltaY,
behavior: 'smooth'
});
};
el.addEventListener('wheel', onWheel);
return () => el.removeEventListener('wheel', onWheel);
}
}, []);
return elRef;
}
It's conditionally preventing default behavior only when there is space to scroll in that direction, so when there is no space to scroll, for example the whole page will start to scroll. The change is here:
if (
!(el.scrollLeft === 0 && e.deltaY < 0) &&
!(el.scrollWidth - el.clientWidth - Math.round(el.scrollLeft) === 0 &&
e.deltaY > 0)
) {
e.preventDefault();
}
I can not comment, because my reputation is not enough.
#arVK's answer works, but using 'scrollBy' instead of 'scrollTo' can get smooth wheel.
import { useRef, useEffect } from "react";
export function useHorizontalScroll() {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = e => {
if (e.deltaY == 0) return;
e.preventDefault();
el.scrollBy(e.deltaY, 0);
};
el.addEventListener("wheel", onWheel);
return () => el.removeEventListener("wheel", onWheel);
}
}, []);
return elRef;
}
You can use onWheel event directly:
import React, { useRef } from "react";
export const Content = () => {
const ref = useRef<HTMLDivElement>(null);
const onWheel = (e: UIEvent) => {
const elelemnt = ref.current;
if (elelemnt) {
if (e.deltaY == 0) return;
elelemnt.scrollTo({
left: elelemnt.scrollLeft + e.deltaY,
});
}
};
return (
<div ref={ref} onWheel={onWheel}>
TEST CONTENT
</div>
);
};

Why isn't my scrolling position working in react?

I'm trying to make a sticky nav with an active state while scrolling. So when you're scrolling over each section, the nav has an active state. Kind of like what is seen here:
https://codepen.io/rishabhp/pen/aNXVbQ
The problem I'm having is some of my numbers aren't correct. Here's the code:
handleScroll = () => {
let sections = document.querySelectorAll('.deal-details__container'),
nav = document.querySelectorAll('.overview-nav'),
navHeight = nav[0].clientHeight;
let totalScroll = document.body.getBoundingClientRect().top;
window.addEventListener('scroll', () => {
sections.forEach(section => {
let topOffset = section.getBoundingClientRect().top;
let top = topOffset - navHeight,
bottom = top + section.clientHeight;
if (totalScroll >= top && totalScroll <= bottom) {
this.setState({ activeSection: true });
} else {
this.setState({ activeSection: false });
}
});
});
console.log(totalScroll);
};
For instance my totalScroll inside of the forEach only amounts to -20 or something similar.
I feel like I'm missing something simple. Any thoughts?

Sticky nav bar with native JavaScript only

How would I make a sticky nav bar without using jQuery?
I want to do something like:
if (scrollheight == x)
navbar.addclass "sticky"
I have recently worked on sticky sidebar nav using EmberJS.
Anyway at the end I decided to extract the solution into vanilla javascript based pen as it seems sticky navigation is pretty common feature.
You can see it here: http://codepen.io/moubi/pen/ALpmwy
Github project: https://github.com/moubi/sticky-nav
Here is the js code I am using for the functionality:
// https://github.com/moubi/sticky-nav
const NAV_OFFSET = 30;
class StickyNav {
constructor() {
this.el = null;
this.anchor = null;
this.removedScrollClass = '';
this.events();
}
didRender() {
this.el = document.getElementsByTagName('nav')[0];
this.anchor = document.getElementsByClassName('nav-anchor')[0];
this.removedScrollClass = this.el.className;
this.onResize();
}
onResize() {
var { paddingLeft, paddingRight } = window.getComputedStyle(this.el.parentNode),
parentWidth = this.el.parentNode.offsetWidth - parseInt(paddingLeft) - parseInt(paddingRight);
this.el.style.width = `${parentWidth}px`;
}
onScroll() {
var scroll = Math.max(document.documentElement.scrollTop, document.body.scrollTop),
topOffset = this.anchor.offsetTop - NAV_OFFSET;
if (this.el.className.indexOf('scroll') != -1) {
if (scroll <= topOffset) {
this.el.className = this.removedScrollClass;
}
} else if (scroll >= topOffset) {
this.el.className += ' scroll';
}
}
events() {
window.addEventListener('load', () => { this.didRender(); });
window.addEventListener('scroll', () => { this.onScroll(); });
window.addEventListener('resize', () => { this.onResize(); });
}
}
new StickyNav();
This solution should work for top nav bar as well with some minor adjustments.
In the code I am actually adding scroll class, but in your case it should be sticky - at the end "sticky" sounds more reasonable.

Categories

Resources