JavaScript getting stuck in a loop? - javascript

I am trying to perform a simple animation that I would like to happen after a successful return of an AJAX call.
$('#result').click(function(){
$(document).ajaxSuccess(function(){
$('html, body').on("scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove", function(){
$('html, body').stop();
});
$('html, body').animate({
scrollTop: $('#result').position().top
}, 500, function(){
$('html, body').off("scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove");
});
});
});
The problem is that sometimes, not always, the animation does not seem to want to exit in that it causes jerky movements and a constant return to the top of the div if the user attempts to scroll away. When I remove the ajaxSuccess requirement, the problem does not occur. Any ideas?

There is no need to stop mousewheel, scroll etc. movement by using stop() function.
animate function won't be disturbed until it has reached the document top.
This would do:
$('#result').click(function(){
$(document).ajaxSuccess(function(){
$('html, body').animate({
scrollTop: $('#result').position().top
}, 500);
});
});

You're attaching a new ajaxSuccess callback every time the user clicks on #result.
This means that if the user clicked on #result N times, then N different functions are attached to be executed whenever an ajax request completes successfully. And each function will try to animate $('html, body')
Solution: Move your code out of $('#result').click function or unbind previous handlers before binding a new one.
And here is the simplified example of what is happening. Try to click two buttons one after another:
$("#one").click(() => {
console.log("Clicked First")
$("#two").click(() => console.log("Clicked Second"));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id=one>First</button>
<button id=two>Second</button>

Related

jQUery --> After window resize scroll to section does not work as expected

After window resize my scroll function does not work as I described below (description shows how I want it to work):
I do window resize.
After window resize when I click the given menu item the window should scroll to...
corresponding to that menu item section offset().top-45 for max-width:480px (first breakpoint)
corresponding to that menu item section offset().top-90px for min-width 481px (second breakpoint)
https://jsfiddle.net/d1abevro/1/
It only works as expected for a given breakpoint without window resize (onload).
function displaymenu() {
if ($(window).width() <= 480) {
$('.c-main-menu ul').css({display: 'none'});
$(document).on("click", "a.c-main-menu__link", function(event){
event.preventDefault();
$('html,body').animate({
scrollTop: $($.attr(this, 'href')).offset().top-56
}, 800);
$('.c-main-menu ul').slideToggle();
});
$('.c-nav .menu-trigger').click(function() {
$('.c-nav .c-main-menu ul').slideToggle();
});
} else {
$('.c-main-menu ul').css({display: 'block'});
$(document).on("click", "a.c-main-menu__link", function(event){
event.preventDefault();
$('html,body').animate({
scrollTop: $($.attr(this, 'href')).offset().top-90
}, 800);
});
}
}
That is caused because after each resize you append a new event. You should kill the old event an create a new one in order to prevent conflict with the old event. To get this approach the best way is to adding a namespace for your event, for example:
$(document).on("click.menuLinkEvent",".my_links", function(){});
And before adding the new event kill any old event it using unbind method and passing the event with the namespace:
$(document).unbind("click.menuLinkEvent");
Also looks like you understand correctly that the event will be appended each time after resize so you added a setTimeout function, but forgot to add a time, so however it will fire inmediately.
I made some changes to your fiddler. Let me know if it works as you expect

How to stop scroll event listener after first trigger?

I want a scroll event listener to fire when the user scrolls the page for the first time, but then to stop firing after a certain amount of time and just allow the user to scroll normally.
Here's how I've currently got it working:
var scrollcount = 0
// Scroll event listener
window.addEventListener("scroll", function() {
setTimeout(function() {
scrollcount ++
if (scrollcount < 40) {
window.scrollTo(0, 0);
}
}, 1200);
});
The scrollcount variable increments along with scrolling, and 40 is about how much it takes for one scroll up on my laptop's trackpad. If the counter is under 40, the page scrolls back up to the top of the page once the user lets go of the scroll wheel, if it's over 40 it doesn't.
I realise that this is a really bad way to go about this, so I'm wondering if anyone has a more reliable way to do it. I tried to have a removeEventListener method turn off the event listener once setTimeout has finished its delay, but I couldn't get it to target the window. I think removeEventListener would work if the scroll event listener was assigned to a container div, and not the window, but when I tried that the scroll event listener wouldn't work in the first place.
I wanted to avoid jQuery or any other library if I could, but at this point I'll use anything that gets it to work reliably.
You need to give a name to your listener to remove it:
var scrollcount = 0
// Scroll event listener
function scrollListener() {
setTimeout(function() {
scrollcount ++
if (scrollcount < 40) {
window.scrollTo(0, 0);
window.removeEventListener("scroll", scrollListener);
}
}, 1200);
});
window.addEventListener("scroll", scrollListener);
This will jump to the top of the page, after 2 sec from when user start scrolling:
/* Create a one-time event */
function onetime(node, type, callback) {
node.addEventListener(type, function(e) {
e.target.removeEventListener(e.type, arguments.callee);
return callback(e);
});
}
onetime(document, 'scroll', function(e) {
setTimeout(function(){ window.scrollTo(0, 0); }, 2000);
});
see it live here
If you are using jQuery, you can use jQuery's one function, and simplify the code to:
$(document).one('scroll', function(e) {
setTimeout(function(){ window.scrollTo(0, 0); }, 2000);
});
If you want to jump to the top of the page after some animations are finished instead of waiting predefined amount of time, then you should call window.scrollTo(0, 0); after you are done animating. If you are animating using jQuery's effect functions, then you can pass callback function as last argument, and it will be called once the animation is complete. For example you can do something like this:
$(document).one('scroll', function(e) {
$('pre').animate(
{fontSize: "106px"},
2000,
function(){ window.scrollTo(0, 0); }
);
});
see it live here

Knockout click binding fires after second click

I have a knockout binding that scrolls to a part if the page on click with this function
scrollTo() {
$("a.scroll").click(function(event) {
event.preventDefault();
$('body').animate({
scrollTop: $(this.hash).offset().top
}, 500);
});
}
The html where i call the function
<a class="scroll icon-arrow-down" href="#part" data-bind="localizedText: { id: '4-anchor-1', html: true }, tap: controller.scrollTo.bind(controller)"></a>
The problem is that it fires after the second click and not the first. Also this happen when coming on the page when you click a button it does not fire click it again it fires and then it function normally on other button to.
The issue is because on the first click you call scrollTo() which binds the event. Then on the next and each successive click you add another click() handler as well as execute all previous click event handlers. You just need to remove the scrollTo() function and add the event on load:
<a class="scroll icon-arrow-down" href="#part" data-bind="localizedText: { id: '4-anchor-1', html: true }"></a>
$("a.scroll").click(function(event) {
event.preventDefault();
$('body').animate({
scrollTop: $(this.hash).offset().top
}, 500);
});
Alternatively, if the a element is appended to the DOM dynamically you would need to use a delegated event handler:
$(document).on('click', "a.scroll", function(event) {
event.preventDefault();
$('body').animate({
scrollTop: $(this.hash).offset().top
}, 500);
});
Since you're using Knockout, don't use the jQuery event bindings. Assuming you have defined a tap binding handler (which is to say, to call an event handler like click does), your HTML markup is fine. Just turn your scrollTo method into an event handler.
scrollTo(event) {
event.preventDefault();
$('body').animate({
scrollTop: $(this.hash).offset().top
}, 500);
}

ScrollTo working with all browsers but complete callback fires multiple times

I have read this is the correct way to get scroll to and animation working in Jquery with the body and html tags. However, this also fires the callback multiple times event if $("body, html") shows only two items in a list. At the most I would think 2, after each iteration it can go up which I'm not sure why, but I need to execute the callback one time with his setup? Any fix?
$("body, html").animate({ scrollTop: top }, function () {
animateScroll($topItem);
});
With $("body, html") you're selecting two elements to animate, first body and second html. That's because two callbacks are fired, just like it should be.
See explanation here: Callback of .animate() gets called twice jquery
Try to change your code as follows:
$("html body").animate({ scrollTop: top }, function () {
animateScroll($topItem);
});
you can try this workaround:
var callbackFired=false;
$("body, html").animate({ scrollTop: top }, function () {
if(!callbackFired){
animateScroll($topItem);
callbackFired=true;
}
});
This did it for me.
$("body, html").animate({ scrollTop: top }).promise().done(function() {
animateScroll($topItem);
});

jQuery animation triggers callback twice when run

I'm animating a scroll to effect using jQuery, after the animation ends, I trigger an effect, for some reason the effect is triggered twice, how can I prevent it from happening?
$('.something').on('click', function() {
$('html, body').animate({
scrollTop: $('footer').offset().top
}, {
queue: false,
duration: 1500,
complete: function() {
$('.foo').toggleClass('active');
$('.bar').slideToggle();
}
});
return false;
});
The slideToggle effect seems to be triggered twice.
I have it animate on html & body because animate from 'body' doesn't work in IE8.
Since you're triggering two animations (on html and body) the complete callback should be called twice.
You might want to on trigger the animation on html only if it's required
var animateOn = isIE8 ? 'html' : 'body';
$(animateOn).animate();

Categories

Resources