Adding class to element after scroll - javascript

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

Related

How to tell if the user is scrolling down or up [duplicate]

So I am trying to use the JavaScript on scroll to call a function. But I wanted to know if I could detect the direction of the the scroll without using jQuery. If not then are there any workarounds?
I was thinking of just putting a 'to top' button but would like to avoid that if I could.
I have now just tried using this code but it didn't work:
if document.body.scrollTop <= 0 {
alert ("scrolling down")
} else {
alert ("scrolling up")
}
It can be detected by storing the previous scrollTop value and comparing the current scrollTop value with it.
JavaScript :
var lastScrollTop = 0;
// element should be replaced with the actual target element on which you have applied scroll, use window in case of no target element.
element.addEventListener("scroll", function(){ // or window.addEventListener("scroll"....
var st = window.pageYOffset || document.documentElement.scrollTop; // Credits: "https://github.com/qeremy/so/blob/master/so.dom.js#L426"
if (st > lastScrollTop) {
// downscroll code
} else if (st < lastScrollTop) {
// upscroll code
} // else was horizontal scroll
lastScrollTop = st <= 0 ? 0 : st; // For Mobile or negative scrolling
}, false);
Simple way to catch all scroll events (touch and wheel)
window.onscroll = function(e) {
// print "false" if direction is down and "true" if up
console.log(this.oldScroll > this.scrollY);
this.oldScroll = this.scrollY;
}
Use this to find the scroll direction. This is only to find the direction of the Vertical Scroll. Supports all cross browsers.
var scrollableElement = document.body; //document.getElementById('scrollableElement');
scrollableElement.addEventListener('wheel', checkScrollDirection);
function checkScrollDirection(event) {
if (checkScrollDirectionIsUp(event)) {
console.log('UP');
} else {
console.log('Down');
}
}
function checkScrollDirectionIsUp(event) {
if (event.wheelDelta) {
return event.wheelDelta > 0;
}
return event.deltaY < 0;
}
Example
You can try doing this.
function scrollDetect(){
var lastScroll = 0;
window.onscroll = function() {
let currentScroll = document.documentElement.scrollTop || document.body.scrollTop; // Get Current Scroll Value
if (currentScroll > 0 && lastScroll <= currentScroll){
lastScroll = currentScroll;
document.getElementById("scrollLoc").innerHTML = "Scrolling DOWN";
}else{
lastScroll = currentScroll;
document.getElementById("scrollLoc").innerHTML = "Scrolling UP";
}
};
}
scrollDetect();
html,body{
height:100%;
width:100%;
margin:0;
padding:0;
}
.cont{
height:100%;
width:100%;
}
.item{
margin:0;
padding:0;
height:100%;
width:100%;
background: #ffad33;
}
.red{
background: red;
}
p{
position:fixed;
font-size:25px;
top:5%;
left:5%;
}
<div class="cont">
<div class="item"></div>
<div class="item red"></div>
<p id="scrollLoc">0</p>
</div>
Initialize an oldValue
Get the newValue by listening to the event
Subtract the two
Conclude from the result
Update oldValue with the newValue
// Initialization
let oldValue = 0;
//Listening on the event
window.addEventListener('scroll', function(e){
// Get the new Value
newValue = window.pageYOffset;
//Subtract the two and conclude
if(oldValue - newValue < 0){
console.log("Up");
} else if(oldValue - newValue > 0){
console.log("Down");
}
// Update the old value
oldValue = newValue;
});
This is an addition to what prateek has answered.There seems to be a glitch in the code in IE so i decided to modify it a bit nothing fancy(just another condition)
$('document').ready(function() {
var lastScrollTop = 0;
$(window).scroll(function(event){
var st = $(this).scrollTop();
if (st > lastScrollTop){
console.log("down")
}
else if(st == lastScrollTop)
{
//do nothing
//In IE this is an important condition because there seems to be some instances where the last scrollTop is equal to the new one
}
else {
console.log("up")
}
lastScrollTop = st;
});});
While the accepted answer works, it is worth noting that this will fire at a high rate. This can cause performance issues for computationally expensive operations.
The recommendation from MDN is to throttle the events. Below is a modification of their sample, enhanced to detect scroll direction.
Modified from: https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
// ## function declaration
function scrollEventThrottle(fn) {
let last_known_scroll_position = 0;
let ticking = false;
window.addEventListener("scroll", function () {
let previous_known_scroll_position = last_known_scroll_position;
last_known_scroll_position = window.scrollY;
if (!ticking) {
window.requestAnimationFrame(function () {
fn(last_known_scroll_position, previous_known_scroll_position);
ticking = false;
});
ticking = true;
}
});
}
// ## function invocation
scrollEventThrottle((scrollPos, previousScrollPos) => {
if (previousScrollPos > scrollPos) {
console.log("going up");
} else {
console.log("going down");
}
});
This simple code would work: Check the console for results.
let scroll_position = 0;
let scroll_direction;
window.addEventListener('scroll', function(e){
scroll_direction = (document.body.getBoundingClientRect()).top > scroll_position ? 'up' : 'down';
scroll_position = (document.body.getBoundingClientRect()).top;
console.log(scroll_direction);
});
You can get the scrollbar position using document.documentElement.scrollTop. And then it is simply matter of comparing it to the previous position.
If anyone looking to achieve it with React hooks
const [scrollStatus, setScrollStatus] = useState({
scrollDirection: null,
scrollPos: 0,
});
useEffect(() => {
window.addEventListener("scroll", handleScrollDocument);
return () => window.removeEventListener("scroll", handleScrollDocument);
}, []);
function handleScrollDocument() {
setScrollStatus((prev) => { // to get 'previous' value of state
return {
scrollDirection:
document.body.getBoundingClientRect().top > prev.scrollPos
? "up"
: "down",
scrollPos: document.body.getBoundingClientRect().top,
};
});
}
console.log(scrollStatus.scrollDirection)
I personally use this code to detect scroll direction in javascript...
Just you have to define a variable to store lastscrollvalue and then use this if&else
let lastscrollvalue;
function headeronscroll() {
// document on which scroll event will occur
var a = document.querySelector('.refcontainer');
if (lastscrollvalue == undefined) {
lastscrollvalue = a.scrollTop;
// sets lastscrollvalue
} else if (a.scrollTop > lastscrollvalue) {
// downscroll rules will be here
lastscrollvalue = a.scrollTop;
} else if (a.scrollTop < lastscrollvalue) {
// upscroll rules will be here
lastscrollvalue = a.scrollTop;
}
}
Modifying Prateek's answer, if there is no change in lastScrollTop, then it would be a horizontal scroll (with overflow in the x direction, can be used by using horizontal scrollbars with a mouse or using scrollwheel + shift.
const containerElm = document.getElementById("container");
let lastScrollTop = containerElm.scrollTop;
containerElm.addEventListener("scroll", (evt) => {
const st = containerElm.scrollTop;
if (st > lastScrollTop) {
console.log("down scroll");
} else if (st < lastScrollTop) {
console.log("up scroll");
} else {
console.log("horizontal scroll");
}
lastScrollTop = Math.max(st, 0); // For mobile or negative scrolling
});
This seems to be working fine.
document.addEventListener('DOMContentLoaded', () => {
var scrollDirectionDown;
scrollDirectionDown = true;
window.addEventListener('scroll', () => {
if (this.oldScroll > this.scrollY) {
scrollDirectionDown = false;
} else {
scrollDirectionDown = true;
}
this.oldScroll = this.scrollY;
// test
if (scrollDirectionDown) {
console.log('scrolling down');
} else {
console.log('scrolling up');
}
});
});
Sometimes there are inconsistencies in scrolling behavior which does not properly update the scrollTop attribute of an element. It would be safer to put some threshold value before deciding the scroll direction.
let lastScroll = 0
let threshold = 10 // must scroll by 10 units to know the direction of scrolling
element.addEventListener("scroll", () => {
let newScroll = element.scrollTop
if (newScroll - lastScroll > threshold) {
// "up" code here
} else if (newScroll - lastScroll < -threshold) {
// "down" code here
}
lastScroll = newScroll
})
let arrayScroll = [];
window.addEventListener('scroll', ()=>{
arrayScroll.splice(1); //deleting unnecessary data so that array does not get too big
arrayScroll.unshift(Math.round(window.scrollY));
if(arrayScroll[0] > arrayScroll[1]){
console.log('scrolling down');
} else{
console.log('scrolling up');
}
})
I have self-made the above solution. I am not sure if this solution may cause any considerable performance issue comparing other solutions as I have just started learning JS and not yet have completed my begginer course. Any suggestion or advice from experienced coder is highly appriciated. ThankYou!

How to add a class in vanilla JS for a limited time?

I'd like to disable scrolling in JS for a few seconds. Right now I have this, but everytime I scroll the page it blocks for one second then the blocking stops, then I scroll and it blocks again, etc.
I'd like to have the blocking stopped only once after I scroll the page. This is my code:
HTML
<div class="cover" id="cover">
...
</div>
<div class="container" id="container">
...
</div>
JS
var scrollPosition = window.scrollY;
var cover = document.getElementsByClassName("cover")[0];
window.addEventListener("scroll", function () {
scrollPosition = window.scrollY;
if (scrollPosition >= 1) {
cover.classList.add("cover-close");
document.body.classList.add("stop-scrolling");
setTimeout(function() {
document.body.classList.remove("stop-scrolling");
}, 1000);
} else {
cover.classList.remove("cover-close");
}
});
Sass
.stop-scrolling
height: 100%
overflow: hidden
Someone has a solution? Thanks!
I'd add a variable that checks if you've scrolled and blocked before.
var scrollPosition = window.scrollY;
var cover = document.getElementsByClassName("cover")[0];
var hasBlocked = false;
window.addEventListener("scroll", function () {
scrollPosition = window.scrollY;
if (scrollPosition >= 1) {
cover.classList.add("cover-close");
if (!hasBlocked) {
hasBlocked = true;
document.body.classList.add("stop-scrolling");
setTimeout(function() {
document.body.classList.remove("stop-scrolling");
}, 1000);
}
} else {
cover.classList.remove("cover-close");
}
});

jQuery scrollTop() returns wrong offset on scroll-direction change

I'm trying to get the correct scroll direction via jQuery's "scroll" event.
For this, I'm using the solution here: https://stackoverflow.com/a/4326907/8407840
However, if I change the direction of my scroll, the offset returned by scrollTop is incorrect on the first time. This results in the following behavior:
Wheel down -> down
Wheel down -> down
Wheel up -> down
Wheel up -> up
Wheel down -> up
Wheel down -> down
... and so on, I think you get it.
var ACTIVE_SECTION = null;
var ANIMATION_DURATION = 700;
$(document).ready(function() {
ACTIVE_SECTION = $("section:first-of-type").get(0);
var prevPosition = $(window).scrollTop();
$(window).on("scroll", function() {
doScrollingStuff(prevPosition);
});
});
function doScrollingStuff(prevPosition) {
var ctPosition = $(window).scrollTop();
var nextSection = ACTIVE_SECTION;
// Remove and re-append event, to prevent it from firing too often.
$(window).off("scroll");
setTimeout(function() {
$(window).on("scroll", function() {
doScrollingStuff(prevPosition);
});
}, ANIMATION_DURATION + 100);
// Determine scroll direction and target the next section
if(ctPosition < prevPosition) {
console.log("up");
nextSection = $(ACTIVE_SECTION).prev("section").get(0);
} else if(ctPosition > prevPosition) {
console.log("down");
nextSection = $(ACTIVE_SECTION).next("section").get(0);
}
// If a next section exists: Scroll to it!
if(typeof nextSection != 'undefined') {
var offset = $(nextSection).offset();
$("body, html").animate({
scrollTop: offset.top
}, ANIMATION_DURATION);
ACTIVE_SECTION = nextSection;
} else {
nextSection = ACTIVE_SECTION;
}
console.log(ACTIVE_SECTION);
prevPosition = ctPosition;
}
section {
width:100%;
height:100vh;
padding:60px;
box-sizing:border-box;
}
section:nth-child(1) { background:#13F399; }
section:nth-child(2) { background:#14FD43; }
section:nth-child(3) { background:#4EE61E; }
section:nth-child(4) { background:#BEFD14; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section id="sect1">Section 1</section>
<section id="sect2">Section 2</section>
<section id="sect3">Section 3</section>
<section id="sect4">Section 4</section>
Here's a pen, where you can see my implementation: https://codepen.io/EigenDerArtige/pen/aVEyxd
I am trying to accomplish an autoscroll to the next or previous section, whenever the user scrolls or swipes up/down... Therefore I only fire the "scroll"-event once every second, to prevent multiple scrolljacks all happening at once... However the above behavior seems to result in the user being scrolled to the wrong section.
I've been trying for a couple of hours now to get it working, but to no avail. Help is greatly appreciated!
The problem lies in the assignment prevPosition = ctPosition.
Each time the scroll handler runs, var ctPosition = $(window).scrollTop(); is good for determining scroll direction, however it's not the value that should be rememberad as prevPosition.
prevPosition needs to be $(window).scrollTop() as measured after the animation has completed.
Try this :
$(document).ready(function() {
var ANIMATION_DURATION = 700;
var ACTIVE_SECTION = $("section:first-of-type").eq(0);
var prevPosition = $(window).scrollTop();
$(window).on("scroll", doScrollingStuff);
function doScrollingStuff(e) {
$(window).off("scroll");
var ctPosition = $(window).scrollTop();
var nextSection = (ctPosition < prevPosition) ? ACTIVE_SECTION.prev("section") : (ctPosition > prevPosition) ? ACTIVE_SECTION.next("section") : ACTIVE_SECTION; // Determine scroll direction and target the next section
// If next section exists and is not current section: Scroll to it!
if(nextSection.length > 0 && nextSection !== ACTIVE_SECTION) {
$("body, html").animate({
'scrollTop': nextSection.offset().top
}, ANIMATION_DURATION).promise().then(function() {
// when animation is complete
prevPosition = $(window).scrollTop(); // remember remeasured .scrollTop()
ACTIVE_SECTION = nextSection; // remember active section
$(window).on("scroll", doScrollingStuff); // no need for additional delay after animation
});
} else {
setTimeout(function() {
$(window).on("scroll", doScrollingStuff);
}, 100); // Debounce
}
}
});

JS Smooth Scroll Vertical & Horizontal Function

I want to create a multisite in a one-page, where everytime a link is clicked, it automatically scrolls to that element in the page (all div elements).
The function works but it still jumps to the given element.
Here's the code I've got so far and the elements I use to call the function:
<li class="topli">
<a id="toplink" onclick="Scroll('#home')" href="javascript:void(0);">HOME</a>
</li>
<script>
function Scroll(element) {
var ID = element.split('#').join('');
var target = document.getElementById(ID);
var offset = target.getBoundingClientRect();
console.log("X:",offset.x,"Y:",offset.y);
if (window.scrollY != offset.y) {
window.scroll(window.scrollY, offset.y);
}
if (window.scrollX != offset.x) {
window.scroll(window.scrollX, offset.x);
}
}
</script>
If needed I'll add a more detailed code to a JSFiddle.
Create jQuery helper for this
(function($) {
$.fn.goTo = function() {
$('html, body').animate({
scrollTop: $(this).offset().top + 'px'
}, 'fast');
return this;
}
})(jQuery);
And use like
$('#el').goTo();
Try this for scrolling vertically (where 100 is the rate of scroll):
const goTo = (targetEl) => {
const elm = document.querySelector(targetEl);
const offset = elm.getBoundingClientRect().bottom;
if (offset > window.innerHeight) {
window.scroll(0, window.scrollY + 100);
setTimeout(() => {
goTo(targetEl);
}, 16.666);
} else {
return;
}
};
Call it like so:
goTo('#scroll-target');
or on click:
window.addEventListener('DOMContentLoaded', () => {
document.querySelector('.long-div').addEventListener('click', () => {
goTo('#scroll-target');
});
});
Vertical smooth scroll, easy and native way:
let element = document.querySelector('#element');
// Vertical Scroll
this.element.scrollTo({
left: element.offsetLeft,
behavior: 'smooth'
})
// Horizontal Scroll
element.scrollIntoView({block: "end", behavior: "smooth"});
docs:
https://developer.mozilla.org/pt-BR/docs/Web/API/Element/scrollIntoView
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo

Javascript Scrolltop for fixed menu position

I want to stick my menu bar after scrolling few pixels in javascript but it is not working as it should. Here is my script.
JavaScript Code:
window.onscroll = function() {stick()};
function stick() {
if (document.body.scrollTop || document.documentElement.scrollTop > 150) {
document.getElementById("test").style.position = "fixed";
document.getElementById("test").style.background = "blue";
} else {
document.getElementById("test").style.position = "initial";
}
}
Problem:
Whenever I scroll even one pixel my test id get fixed. I tried to change scrollTop value in if but nothing effective. Any attention will be appreciated.
Update:
Here is also error using ids.
<script>
document.getElementById("cntnr").onscroll = function() {stick()};
function stick() {
if (document.getElementById("cntnr").scrollTop > 119) {
document.getElementById("navdwn").style.position = "fixed";
} else {
document.getElementById("navdwn").style.position = "initial";
}
}
</script>
Verify your If condition, to fix the menu current document scrolled to 150px top
change:
window.onscroll = function() {stick()};
function stick() {
if (document.body.scrollTop > 150 ) {
document.getElementById("test").style.position = "fixed";
document.getElementById("test").style.background = "blue";
} else {
document.getElementById("test").style.position = "initial";
}
}
The value of document.body.scrollTop will be greater than 0 as soon as you start scrolling. The OR operator will not check for the second condition if the first condition is true.
Hence as soon as you start scrolling the first condition becomes true and then the test id gets fixed.
Change the condition to if (document.body.scrollTop > 150 ) {.... and it will work.
try this code, you need the parseInt() function of the javascript to make the
document.documentElement.scrollTop equal to integer
window.onscroll = function() {stick()};
var offset = document.documentElement.scrollTop
function stick() {
if (parseInt(offset) > 150) {
document.getElementById("test").style.position = "fixed";
document.getElementById("test").style.background = "blue";
} else {
document.getElementById("test").style.position = "initial";
}
}

Categories

Resources