Jumpy scroll after scroll-animation with javascript - javascript

I have a problem with the scrolling animation. Jumpy scroll occurs when the page is scrolled after scroll-animation. I suspected the scroll-event repeats itself, but I'm not sure. Can you help me with it?
$(document).ready(function(){
var offset;
var anchor = $("#navigation").offset().top;
$(window).bind('mousewheel', function (e) {
offset = $(window).scrollTop();
if (e.originalEvent.wheelDelta < 0) {
//mouse scroll down
console.log('Down: ' + offset + " " + anchor);
if (offset >= anchor) {
// if anchor has been scrolled, user can scroll further
// the problem ocuurs in this block
return true;
} else {
// animate to anchor( nav menu)
$("body, html").animate({
scrollTop: anchor + 1
}, 200);
$("#navigation").addClass("nav-fixed");
return false;
}
} else {
//mouse scroll up
if (offset < anchor) {
$("#navigation").removeClass("nav-fixed");
return true;
}
}});
});
JSFiddle: http://jsfiddle.net/0noms3cs/
Thank you a lot!

Your issue is simple. The scroll event fires over and over again. Your line of thinking behind the cause of this issue is correct, you have a large number of animate events that get stacked up which causes this weird behavior.
You can resolve this issue by adding a boolean variable (such as scrollInitialized) that starts out as false and gets flipped to true once the scroll event has fired once.
Here's the altered JS code. Note: I only added the scrollInitialized variable and a check for it in the if statement.
Edit: I also removed the inner if-else case since it was not necessary using this design.
EDIT 2: I originally misunderstood what you wanted to do. What you need to do was add a scrollLock variable that would only be set to true for the duration of your animation. After thinking about this, I implemented it for you. Here is the Jsfiddle:
https://jsfiddle.net/04gaaapo/1/
Here is the new JS code:
$(document).ready(function () {
var scrollLock = false;
var offset;
var anchor = $("#navigation").offset().top;
$(window).bind('mousewheel', function (e) {
offset = $(window).scrollTop();
// if scroll is NOT locked and we are above the anchor
if (scrollLock === false && offset < anchor) {
if (e.originalEvent.wheelDelta < 0) {
// scrolling down
scrollLock = true;
// animate to anchor( nav menu)
$("body, html").animate({
scrollTop: anchor + 1
}, 200);
// unlock in 250ms
setTimeout(toggleLock, 250);
// add nav class
$("#navigation").addClass("nav-fixed");
} else if (e.originalEvent.wheelDelta > 0) {
// scrolling up
scrollLock = true;
// animate to top of page
$("body, html").animate({
scrollTop: 0
}, 200);
// unlock in 250ms
setTimeout(toggleLock, 250);
// remove nav class
$("#navigation").removeClass("nav-fixed");
}
}
});
function toggleLock() {
scrollLock = !scrollLock;
};
});

Related

Scroll animation behaves erratically going up and down, possible input lag on mousewheel

I'm trying to trigger a smooth scroll to an element at the end of the page and another one to the top of the page everytime I move the mousewheel respectively down or up. The two parts have both height:100vh.
The thing is that once it goes down it starts to behave randomly.
I feel like I need to interrupt the animation completely after the scroll is completed because it "fights with itself" struggling to go back up and vice versa. Of course I could be easiy wrong, I'm trying to learn the way.
Is there some performance issue? Maybe it is unable to get the inputs in time? Something is overlapping? It seems like there's some sort of cooldown before I can scroll again. This is what I'm trying to understand. Thanks
jQuery(window).bind('mousewheel DOMMouseScroll', function(event){
if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) {
// scroll up
console.log("scroll up");
jQuery('html,body').animate({scrollTop: jQuery("#top").offset().top}, 1200, 'linear');
}
else {
// scroll down
console.log("scroll down");
jQuery('html,body').animate({scrollTop: jQuery("#bottom").offset().top}, 1200, 'linear');
}
});
Same thing with this, here I'm using the Jquery.scrollTo library
jQuery(window).bind('mousewheel DOMMouseScroll', function(event){
if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) {
// scroll up
console.log("scroll up");
jQuery('body').scrollTo('#top', {duration:1200});
}
else {
// scroll down
console.log("scroll down");
jQuery('body').scrollTo('#bottom', {duration:1200});
}
});
Here's the html for completeness:
<div id="top" style="height:100vh;background-color: #2196f3;"></div>
<div id="bottom" style="height:100vh;background-color: #009688;"></div>
EDIT:
If I move the mousewheel just the bare minimum it works perfectly both ways so the problem is input overlapping, in other words I need a way to send just the first scroll input and not the entire scroll otherwise too many inputs make the script "crash".
here's a working example, try to scroll up and down:
https://jsfiddle.net/mr8hnxbd/
Calling .stop(true) works, but I believe it might cause issues mid animation if you keep scrolling in the direction of the animation, extending the duration of the animation. Alternatively you can do the following to ensure the animation completes before doing another animation.
(function(jQuery) {
let position = '';
let scrolling = false;
let animationDuration = 1200;
jQuery(window).bind('mousewheel', function(event){
if (event.originalEvent.wheelDelta > 0) {
// scroll up
if (scrolling || position === 'top')
return;
//console.log("scroll up");
scrolling = true; //Prevents any scrolling when scrolling is active
position = 'top'; //Prevents scrolling up when already at the top
jQuery('html,body').animate(
{
scrollTop: jQuery("#top").offset().top
},
animationDuration,
'linear',
function () {
scrolling = false; //After the animation is complete, set scroling to false
},
);
}
else {
// scroll down
if (scrolling || position === 'bottom')
return;
//console.log("scroll down");
scrolling = true;
position = 'bottom';
jQuery('html,body').animate(
{
scrollTop: jQuery("#bottom").offset().top
},
animationDuration,
'linear',
function () {
scrolling = false;
},
);
}
});
})($);
<div id="top" style="height:100vh;background-color: #2196f3;"></div>
<div id="bottom" style="height:100vh;background-color: #009688;"></div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

ScrollTop with button & scroll with JQuery

I have a website with two scroll options. When you scroll down, it scrolls to the anchor Point 1.
I also have a Button which jumps to the same anchor point.
My problem: When I click the Button, the site jumps to the Anchor, but because there are two ways to the anchor, it triggers the first scroll option as well.
jQuery(document).ready(function($) {
var flag = true;
$(window).scroll(function () {
if (flag == true) {
scroll = $(window).scrollTop();
if (scroll > 50) $('#scroll-down')[0].click();
flag = false;
}
});
$(window).scroll(function () {
if (flag == false) {
scroll = $(window).scrollTop();
if (scroll < 50 )
flag = true;
}
});
});
Any solutions for this ?
From the screencast you sent, this code should scroll to the bottom of the banner when the button is clicked (provided you correctly place the anchor div):
jQuery(document).ready(function ($) {
// The button is assumed to have an id of 'scroll-down' - triggered when clicked
$('#scroll-down').on('click', function () {
// Move to the pre-defined anchor point
// Insert <div id="scroll-down-anchor"></div> to where you want to scroll to
$('html, body').animate({
scrollTop: $('[id=scroll-down-anchor]').position().top
// Set the speed of the scroll here (currently set to 1000 ms)
}, 1000);
});
});
I'm still not sure from the screencast what you want to do with the behaviour based on the window position when the window is scrolled.
UPDATE: In light of the screencast and further information.
The code has been updated, BUT, although this is, I think, what your code was trying to achieve, I don't think the effect is very nice at all because you're intercepting a user's intention, hijacking it, and making something different happen. It's also very choppy, and to improve that would probably take many more lines of code (eg to determine speed of existing scroll, intercept that and make it accelerate organically - way beyond the scope of this kind of answer). Maybe there's a plugin out there to do this nicely.
Anyway, I think this code completes what you were trying to achieve, but the end effect, although subjective, is not very nice in my opinion. I've put in explanatory comments:
jQuery(document).ready(function ($) {
// Variable to store scrolling state
var scrolling = false;
// Variable to store position to determine whether scrolling up or scrolling down
var previousScroll = 0;
$(window).scroll(function () {
// Only is state is not 'scrolling' (ie not to run if button has been clicked)
if (scrolling === false) {
// Get position
var currentScroll = $(this).scrollTop();
// Compare position to stored variable: scrolling up or scrolling down
if (currentScroll > previousScroll) {
// Determine if position is 'within the zone' - set here to 50px
if (currentScroll < 50 && currentScroll !== 0) {
console.log('IN ZONE');
// Trigger button click
$('#scroll-down').trigger('click');
} else {
// Normal scrolling down code, outside zone
console.log('SCROLLING DOWN ');
}
}
else {
// Scrolling up code
console.log('SCROLLING UP ');
}
// Set variable for comparison of next scroll event
previousScroll = currentScroll;
}
});
// The button is assumed to have an id of 'scroll-down' - triggered when clicked
$('#scroll-down').on('click', function () {
// Set the scrolling state
scrolling = true;
// Animate with callback to set scrolling back to 'true' when complete
$('html, body').animate({ scrollTop: $('[id=scroll-down-anchor]').position().top }, 1000, function () {
// Callback code - set scrolling state to be false when animation has finished
scrolling = false;
});
});
});

If/Else Jquery Header animations

I am having some trouble with this one. I have the following javascript...
$(window).on('scroll', function() {
scrollPosition = $(this).scrollTop();
if (scrollPosition >= 130) {
header.animate({height: '51px'}, 500);
mainNav.css("display", "none");
logo.fadeOut("fast");
scrollLogo.delay(300).slideDown("fast");
$(this).off('scroll');
}
else {
header.animate({height: '130px'}, 500);
}
});
I am trying to do a series of changes to my header when scrolled past 130 px and then return it to its original state if scrolled back up. Everything works fine if I comment out the else statement, but as soon as I start to add to it, everything breaks. I cannot figure out why. Any help is much appreciated.
My guess would be that you're repeatedly calling either the if or the else condition, whereas you just want to call them just once.
Try something like this:
var compactHeader = false;
$(window).on('scroll', function() {
scrollPosition = $(this).scrollTop();
if (scrollPosition >= 130 && !compactHeader) {
compactHeader = true;
// Code to collapse header...
} else if (scrollPosition < 130 && compactHeader) {
compactHeader = false;
// Code to expand header...
}
});
As well as checking the scroll position we check whether the header is collapsed or expanded, to ensure the animation is only performed once.

How to determine scroll direction without actually scrolling

I am coding a page where the first time the user scrolls, it doesn't actually scroll the page down, instead it adds a class with a transition.
I'd like to detect when the user is scrolling down, because if he scrolls up, I want it to do something else.
All the methods that I've found are based on defining the current body ScrollTop, and then comparing with the body scrollTop after the page scrolls, defining the direction, but since the page doesn't actually scroll, the body scrollTop() doesn't change.
animationIsDone = false;
function preventScroll(e) {
e.preventDefault();
e.stopPropagation();
}
$('body').on('mousewheel', function(e) {
if (animationIsDone === false) {
$("#main-header").removeClass("yellow-overlay").addClass("yellow-overlay-darker");
$(".site-info").first().addClass("is-description-visible");
preventScroll(e);
setTimeout(function() {
animationIsDone = true;
}, 1000);
}
});
This is what I have come with, but that way it doesn't matter the direction I scroll it triggers the event
The mousewheel event is quickly becoming obsolete. You should use wheel event instead.
This would also easily allow you to the vertical and/or horizontal scroll direction without scroll bars.
This event has support in all current major browsers and should remain the standard far into the future.
Here is a demo:
window.addEventListener('wheel', function(event)
{
if (event.deltaY < 0)
{
console.log('scrolling up');
document.getElementById('status').textContent= 'scrolling up';
}
else if (event.deltaY > 0)
{
console.log('scrolling down');
document.getElementById('status').textContent= 'scrolling down';
}
});
<div id="status"></div>
Try This using addEventListener.
window.addEventListener('mousewheel', function(e){
wDelta = e.wheelDelta < 0 ? 'down' : 'up';
console.log(wDelta);
});
Demo
Update:
As mentioned in one of the answers, the mousewheel event is depreciated. You should use the wheel event instead.
I know this post is from 5 years ago but I didn't see any good Jquery answer (the .on('mousewheel') doesn't work for me...)
Simple answer with jquery, and use window instead of body to be sure you are taking scroll event :
$(window).on('wheel', function(e) {
var scroll = e.originalEvent.deltaY < 0 ? 'up' : 'down';
console.log(scroll);
});
Try using e.wheelDelta
var animationIsDone = false, scrollDirection = 0;
function preventScroll(e) {
e.preventDefault();
e.stopPropagation();
}
$('body').on('mousewheel', function(e) {
if (e.wheelDelta >= 0) {
console.log('Scroll up'); //your scroll data here
}
else {
console.log('Scroll down'); //your scroll data here
}
if (animationIsDone === false) {
$("#main-header").removeClass("yellow-overlay").addClass("yellow-overlay-darker");
$(".site-info").first().addClass("is-description-visible");
preventScroll(e);
setTimeout(function() {
animationIsDone = true;
}, 1000);
}
});
Note: remember that MouseWheel is deprecated and not supported in FireFox
this one work in react app
<p onWheel={this.onMouseWheel}></p>
after add event listener, in function u can use deltaY To capture mouse Wheel
onMouseWheel = (e) => {
e.deltaY > 0
? console.log("Down")
: console.log("up")
}
Tested on chrome and
$('body').on('mousewheel', function(e) {
if (e.originalEvent.deltaY >= 0) {
console.log('Scroll up'); //your scroll data here
}
else {
console.log('Scroll down'); //your scroll data here
}
});

Prevent scrollTop from calling scroll event

I'm trying to create this behavior: when user scrolls a mousewheel (or presses ↓) the webpage is scrolled down by the height of the window.
I've ended up with following code:
var newScrollTop,
oldScrollTop = $(window).scrollTop(),
preventScroll = false;
$(window).scroll(function() {
if (!preventScroll) {
preventScroll = true;
newScrollTop = $(this).scrollTop();
if (newScrollTop > oldScrollTop) {
$(this).scrollTop( oldScrollTop + $(window).height() );
}
else {
$(this).scrollTop( oldScrollTop - $(window).height() );
}
oldScrollTop = newScrollTop;
preventScroll = false;
}
});
But this doesn't work as I expect it: on scroll event page is scrolled to the very edge (bottom or top). What am I missing?
The issue is you're using scrollTop() which trigger a scroll event inside the window scroll event itself.
So basically, with the code you've written you run into an infinite loop because as soon as the first scroll event is triggered another one is triggered by scrollTop() while your preventScroll variable is still not set to false and so on.
To make your code work you would have to set your preventScroll variable to false inside the setTimeout function like so :
var newScrollTop,
oldScrollTop = $(window).scrollTop(),
preventScroll = false;
$(window).scroll(function() {
if (!preventScroll) {
preventScroll = true;
newScrollTop = $(this).scrollTop();
if (newScrollTop > oldScrollTop) {
$(this).scrollTop( oldScrollTop + $(window).height() );
}
else {
$(this).scrollTop( oldScrollTop - $(window).height() );
}
oldScrollTop = newScrollTop;
setTimeout(function(){ preventScroll = false; }, 0);
}
});
We add some "delay" before we set preventScroll to false.
This way when you call scrollTop() preventScroll variable will still be set to true !
Here's a working fiddle : http://jsfiddle.net/J6Fcm/ (I modified a little bit your code to let the scrolling steps work as expected)
There is no easy way of overriding the default browser scroll functionality. Here's one way of doing what you want, but it requires Brandon Aaron's jquery-mousewheel plugin to manage the mouse scrollwheel:
$(function() {
// Ugly hack to disable browser scrolling (downside: hides scrollbar!!!)
$('html').css('overflow', 'hidden');
// Get viewport height by which to scroll (up or down)
var viewportHeight = $(window).height();
// Recache viewport height on browser resize
$(window).on('resize', function() {
viewportHeight = $(window).height();
});
// Scroll on mousewheel
$('html').on('mousewheel', function(event, delta, deltaX, deltaY) {
// scroll down
if (deltaY < 0)
window.scrollBy(0, viewportHeight);
// scroll up
else
window.scrollBy(0, -viewportHeight);
});
// Disable document keypress event
// which would scroll the content even with "overlow: hidden"
$(document).on('keypress', function(){
return false;
});
// Scroll on arrow up/down keys
$(document).on('keydown', function(event){
// arrow down key
if (event.which == 40)
window.scrollBy(0, viewportHeight);
// arrow up key
if (event.which == 38)
window.scrollBy(0, -viewportHeight);
});
});
Here's a fiddle to demo the code. It all works very well except there is one ugly drawback to my solution. The $('html').css('overflow', 'hidden'); is removing the browser scrollbar.

Categories

Resources