Question
The solution below is intended to slide down the groupDiv displaying div1 and enough space for div2 to slide in. It's all achieved by chaining the animations on the #Link.Click() element.
It seems to bug out, though, when the link is clicked rapidly. Is there a way to prevent this? By perhaps disabling the Click function until the chained animations are complete? I currently have checks in place, but they don't seem to be doing the job :(
Here's the code i'm using:
Custom animate functions.
//Slide up or down and fade in or out
jQuery.fn.fadeThenSlideToggle = function(speed, easing, callback) {
if (this.is(":hidden")) {
visibilityCheck("show", counter--);
return this.slideDown({duration: 500, easing: "easeInOutCirc"}).animate({opacity: 1},700, "easeInOutCirc", callback);
} else {
visibilityCheck("hide", counter++);
return this.fadeTo(450, 0, "easeInOutCirc").slideUp({duration: 500, easing: "easeInOutCirc", complete: callback});
}
};
//Slide off page, or into overflow so it appears hidden.
jQuery.fn.slideLeftToggle = function(speed, easing, callback) {
if (this.css('marginLeft') == "-595px") {
return this.animate({marginLeft: "0"}, speed, easing, callback);
} else {
return this.animate({marginLeft: "-595px"}, speed, easing, callback);
}
};
In the dom ready, i have this:
$('#Link').toggle(
function() {
if (!$("#div2 .tab").is(':animated')) {
$("#GroupDiv").fadeThenSlideToggle(700, "easeInOutCirc", function() {$('#div2 .tab').slideLeftToggle();});
}
},
function(){
if (!$("#groupDiv").is(':animated')) {
$('#div2 .tab').slideLeftToggle(function() {$("#groupDiv").fadeThenSlideToggle(700, "easeInOutCirc", callback);} );
}
}
);
HTML structure is this:
<div id="groupDiv">
<div id="div1">
<div class="tab"></div>
</div>
<div id="div2">
<div class="tab"></div>
</div>
</div>
The issue is your first animating the div#GroupDiv so your initial check if (!$("#div2 .tab").is(':animated')) will be false until the groupDiv has finished animated and the callback is fired.
You could maybe try
if (!$("#div2 .tab").is(':animated') && !$("#GroupDiv").is(':animated'))
however I doubt this will cover really quick clicking. The safest is to unbind the event using
$(this).unbind('toggle').unbind('click');
as the first line inside the if and you can then do away with the animated check. The downside to this is you will have to rebind using the callback you are passing through to your custom animation functions.
You can easily disable your links while animation is running
$('a').click(function () {
if ($(':animated').length) {
return false;
}
});
You can of course replace the $('a') selector to match only some of the links.
Animating something that can be clicked repeatedly is something to look out for because it is prone for errors. I take it that you Problem is that animations queue up and are executed even when you have stopped clicking. The way I solved it was to use the stop() function on an Element.
Syntax: jQuery(selector).stop(clearQueue,gotoEnd) //both parameters are boolean
More Info
When I click on a button, I first stop the animation and clear the Queue, then i proceed to define the new animation on it. gotoEnd can stay false (default value) but you can try tochange it to true if you want, you might like the result.
Usage Example: jQuery('button#clickMe').stop(true).animate({left:+=10}).
you can put this first thing inside the click event
$(element).css({ "pointer-events":"none"});
, and this in the callback function of the animation
$(element).css({ "pointer-events":"auto"});
you can unbind... but this should work too:
if (!$("#div2 .tab").is(':animated') && !$("#GroupDiv").is(':animated')) return;
I have recently made an AJAX jQuery plugin, featuring plenty of animation. The workaround to the AJAX animation bug that I have found is as follows.
$(options.linkSelector).click(function(e){
if ($("#yourNav").hasClass("disabled")) {
return false;
} else {
e.preventDefault();
$("#yourNav").addClass("disabled")
// Prepare DOM for new content
$(content).attr('id', 'content-old');
$('<div/>', {id: 'ajMultiLeft'}).css({'top': '100%'}).insertAfter('#content-old');
// Load new content
$(content).load(linkSrc+ ' ' +options.content+ ' > *', function() {
// Remove old content
$(content).animate({top: '100%'}, 1000, function(){
$(content-old).remove();
$("#yourNav").removeClass("disabled")
});
setBase();
}
What this does is makes the click event for each link respond to nothing whilst the parent div has a class of disabled. The disabled class is set by the function upon initial click and removed via a callback on the final animation.
Related
I want to make a one pager website, without using any third party libraries, like FullPage.js.
when scroll starts --> instead of waiting for the end of the natural scrolling, I want it to take no effect (so no visible scroll caused by the mouse) and to run my code instead. (so it could always go to next section, or previous one, without relying on the amount of the users scroll)
Do you have any idea how could I achieve this? My code snippet waits for the end of scroll, and then jumps to where it should, so it's not working as intended.
(the first section has a "current" class and then the code snippet works by manipulating the 100vh sections by adding/removing this class)
You can see the code snippet I am using below or here:
https://codepen.io/makiwara/pen/PoqjdNZ
Thank you very much for your help, have a nice day!
var script = document.createElement('script');script.src = "https://code.jquery.com/jquery-3.4.1.min.js";document.getElementsByTagName('head')[0].appendChild(script);
var timerId;
var scrollableElement = document.body; //document.getElementById('scrollableElement');
scrollableElement.addEventListener('wheel', checkScrollDirection);
function checkScrollDirection(event) {
var $current = $('.current');
if (checkScrollDirectionIsUp(event)) {
console.log('UP');
$prev = $current.prev();
if ($prev.length) {
clearTimeout(timerId);
timerId = setTimeout(function(){
$current.removeClass('current');
$prev.addClass('current');
$('body,html').animate({
scrollTop: $('.current').offset().top
}, 100);
}, 100)
}
} else {
console.log('Down');
$next = $current.next();
if ($next.length) {
clearTimeout(timerId);
timerId = setTimeout(function(){
$current.removeClass('current');
$next.addClass('current');
$('body,html').animate({
scrollTop: $('.current').offset().top
}, 100);
} , 100)
}
}
}
function checkScrollDirectionIsUp(event) {
if (event.wheelDelta) {
return event.wheelDelta > 0;
}
return event.deltaY < 0;
}
What you need is throttling the event listener, i.e. limit a function call only once per a time period.
What your code is doing is essentially debouncing i.e limit a function call only after a wait time period has passed.
Firstly ditch the timers you're using. You need to somehow block scrolling from happening more than once. The JavaScript part can be easy if you use Underscore.js's throttle function with one caveat though: It passes through subsequent events after the time period has passed. Luckily, its debouncing method accepts a third argument that gives the behavior you'd want:
scrollableElement.addEventListener(
"wheel",
_.debounce(checkScrollDirection, 200, true) // immediately call the function _once_
);
This third argument makes the debounced function behave like a throttled one, that is it will fire only once and at the same time it will fire immediately.
So assuming that your event handler is now free from the original timeout
function checkScrollDirection(event) {
var $current = $(".current");
if (checkScrollDirectionIsUp(event)) {
console.log("UP");
$prev = $current.prev("section");
if ($prev.length) {
$current.removeClass("current");
$prev.addClass("current");
$("body,html").animate(
{
scrollTop: $prev.offset().top
},
100
);
}
} else {
console.log("Down");
$next = $current.next("section");
if ($next.length) {
$current.removeClass("current");
$next.addClass("current");
$("body,html").animate(
{
scrollTop: $next.offset().top
},
100
);
}
}
}
btw, try to get into the habit of specifying selectors inside .next() and .prev() since jQuery will match all possible siblings, which most likely you don't want. In this case, codepen appends additional <script> elements and jQuery will match those as well.
Now if you try this, you'll notice that the window still responds to every scroll event. Scroll events are one of those events that cannot be cancelled so you need to disable it via CSS
The easiest way is to hide the overflow of the body
body { max-height: 100vh; overflow: hidden; }
And that's it. You may need to adjust the throttle waiting time period to match your preferences.
You can find a working version of the codepen here: https://codepen.io/vassiliskrikonis/pen/XWbgxLj
I have a function which fades out a div, loads replacement HTML in, and then fades the div back in. This function is being called (correctly) when I click on the navigation bar at the top to load content into #main div. It is also called on the "about" page to load different team profiles in.
The bug occurs when changing off the default team profile. When clicking to view another profile, the function repeats every "#main" change that has happened before clicking on the profile.
The website is https://symbiohsis.github.io/. The visible bug can be reproduced by clicking "About", then clicking on another profile, e.g "B". The profile flashes but is not selected. Selecting profiles after the first on works fine.
The fade in/out & load function:
/* ajax load into $(sel) from newView (e.g. about.html) */
function loadView(sel, newView, checkOrCb, cb) {
// one of these events will be called when animations end
var animationEnd = "webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend";
// cache element to change
var elem = $(sel);
// if there is a check and callback, do the check
if (cb) {
// if the check fails, exit
if (!checkOrCb(elem, newView))
return 1;
}
// animate out
elem.addClass("animated fadeOut");
// when finished load new page and animate in
elem.one(animationEnd, function() {
// remove fadeOut animation (while keeping elem hidden)
elem.css("opacity", 0).removeClass("fadeOut");
// load new page, then fadeIn
elem.load(newView, function(text, response, ev) {
elem.addClass("fadeIn");
// remove opacity style and animations
elem.one(animationEnd, function() {
elem.css("opacity", "").removeClass("animated fadeIn");
});
// do the callback if one exists
if (cb) {
cb(elem, newView, text, response, ev);
}
else if (checkOrCb) {
checkOrCb(elem, newView, text, response, ev);
}
});
});
}
The navigation bar listeners:
$(".nav_entry").on("click", function() {
loadView("#main",
`${$(this).attr("data-link")}.html`,
function(dummy, newPage) {
return getCurrentPage() != newPage.replace(".html", "");
},
function(dummy, newPage) {
window.location.hash = newPage.replace(".html", "");
});
});
The about listeners:
$(".about_icon").on("click", function() {
var target = $(this);
loadView("#about_info", `about/${this.innerText.toLowerCase()}.md`, function() {
return !target.hasClass("about_selected");
}, function() {
$(".about_selected").removeClass("about_selected");
target.addClass("about_selected");
});
});
// set 2900 team profile as default
$("#about_info").load("about/2900.md");
$(".about_icon:contains(2900)").addClass("about_selected");
How can I fix the bug please? If anyone has any tips on JavaScript conventions that I've missed feel free to add them to your answer/comment :)
This StackOverflow post was what answered my question / fixed the bug.
Q: The transition ends ... but it works 2 times in Google Chrome.
A: This is because Chrome will fire on both thewebkitTransitionEnd and transitionend events.
I use Visual Event to see what (and how many) event listeners were attached to each object. That showed me that there was a lot of end-transition listeners hanging around on #main. I Googled "jquery one not working" and the first result was the answer which is quoted above.
The solution is to have your own alreadyFired variable to make sure it only fires once.
var animationEndFired = false;
elem.addClass("fadeIn");
// remove opacity style and animations
elem.one(animationEnd, function() {
// if only fire once
if (animationEndFired)
return;
animationEndFired = true;
As the title states, I have a function that causes a div to fadeOut
$("#myVid").bind("ended", function() {
//other functions
$(".control").animate( {
marginTop: "+=128px"}, 500 );
$(".control").fadeOut(0);
});
and the one where it fades in
$("#myVid").bind("playing", function() {
//other functions
$(".control").fadeIn(0);
});
why isn't it coming back in? the video is actually an array, so that's why it fade out on ended and back in on playing... can I get some help here?
should this be possible:
$(".control").fadeOut(0).delay(500).fadeIn(0);
because delay()s always give me tons of trouble, and now is just delaying the whole ended function(if in front) or doesn't go first(if in back)
I personally use two functions for when i'm using fades:
function fadeIn(id){
$('#'+id).fadeIn('fade', function() {
});
}
function fadeOut(id){
$('#'+id).fadeOut('fade', function() {
});
}
So you could work with those
I'm using some jQuery code to create tabs in which the page's content is broken up into (navigable from the top of the tab block) and am looking to do the following when a "next" or "previous" link (placed at the bottom of each tab's content) is clicked:
The page to scroll up to the top of the tab block (successfully implemented using ".scrollTo" plugin) over 750ms
Once scrolled, the tab to change to the corresponding "previous" or "next" tab (identified by a hashtag url) - 250ms later.
Using the following code:
$(".external_link").click(function() {
$.scrollTo(515, 750, {easing:'easeInOutQuad'});
setTimeout(changeTab($(this).attr("href")), 1000);
return false;
});
the two happen at the same time at the mo. If anyone could shed some light on what I'm doing wrong I'd be really appreciative.
The code in full:
$(document).ready(function() {
$(".tab_content").hide();
$("ul.content_tabs li:first").addClass("active").show();
$(".tab_content:first").show();
$('.content_tabs li').each(function(i) {
var thisId = $(this).find("a").attr("href");
thisId = thisId.substring(1,thisId.length) + '_top';
$(this).attr("id",thisId);
});
function changeTab(activeTab) {
$("ul.content_tabs li").removeClass("active");
$(activeTab + '_top').addClass("active");
$(".tab_content").hide();
$(activeTab).fadeIn();
}
//check to see if a tab is called onload
if (location.hash!=""){changeTab(location.hash);}
//if you call the page and want to show a tab other than the first, for instance index.html#tab4
$("ul.content_tabs li").click(function() {
if ($(this).hasClass("active"))
return false;
changeTab($(this).find("a").attr("href"));
return false;
});
$(".external_link").click(function() {
$.scrollTo(515, 750, {easing:'easeInOutQuad'});
setTimeout(changeTab($(this).attr("href")), 1000);
return false;
});
});
Am I right to be attempting to do this with setTimeout? My knowledge is incredibly limited.
setTimeout(changeTab($(this).attr("href")), 1000);
That's the wrong one, you have to put in a function, not the result of executing a function, and 250 ms makes more sense. changeTab is a function, changeTab(argument) is executing a function. So try
var that = $(this);
setTimeout(function() {changeTab(that.attr("href"))}, 250);
I think the reason they execute at the same time is because you call the changeTab-function directly when you set the timeout, and the previous function waits for 750ms before proceding.
You are passing a function call to setTimeout(). You need to pass a function reference. The call will get executed immediately, but a function reference will be executed when the timeout expires. Call setTimeout() like this:
setTimeout(function() { changeTab($(this).attr("href")); }, 1000);
Also, you should consider taking advantage of the onAfter option of the .scrollTo() plugin which indicates a function to be called when the scrolling is completed. It may make more sense to go:
$.scrollTo(515, 750, {
easing: 'easeInOutQuad',
onAfter: function () {
setTimeout(function() { changeTab($(this).attr("href")); }, 250);
}
});
I have a slideshow which runs automatically and you can skip to an image by clicking on a button.
It works fine if you click one of the buttons when the image is static, but if you click while the fade functions are running it will run the functions twice which creates some kind of loop which eventually grinds the browser to a stand still!
I know I need to add some kind of "isRunning" flag, but I don't know where.
Here's a link to a jsfiddle - http://jsfiddle.net/N6F55/8/
And code also below...
javascript:
$(document).ready(function() {
var images=new Array();
var locationToRevealCount=6;
var nextimage=2;
var t;
var doubleclick;
addIcons();
function addIcons() {
while (locationToRevealCount>0) {
$("#extraImageButtons").append('<img class="galleryButtons" src="http://www.steveszone.co.uk/images/button_sets/pink_square_button1n.png" alt="'+locationToRevealCount+'" />');
images[locationToRevealCount]='http://www.tombrennand.net/'+locationToRevealCount+'a.jpg';
locationToRevealCount--;
};
$('.homeLeadContent').prepend('<img class="backgroundImage" src="http://www.tombrennand.net/1a.jpg" />');
$("#extraImageButtons img.galleryButtons[alt*='1']").attr("src","http://www.steveszone.co.uk/images/button_sets/black_square_button1n.png");
runSlides();
}
function runSlides() {
clearTimeout(t);
t = setTimeout(doSlideshow,3000);
}
function doSlideshow() {
if($('.backgroundImage').length!=0)
$('.backgroundImage').fadeOut(500,function() {
$('.backgroundImage').remove();
slideshowFadeIn();
});
else
slideshowFadeIn();
}
function slideshowFadeIn() {
if(nextimage>=images.length)
nextimage=1;
$("#extraImageButtons img.galleryButtons").attr("src","http://www.steveszone.co.uk/images/button_sets/pink_square_button1n.png");
$("#extraImageButtons img.galleryButtons[alt*='"+nextimage+"']").attr("src","http://www.steveszone.co.uk/images/button_sets/black_square_button1n.png");
$('.homeLeadContent').prepend($('<img class="backgroundImage" src="'+images[nextimage]+'" style="display:none;">').fadeIn(500,function() {
nextimage++;
runSlides();
}));
}
$("#extraImageButtons img.galleryButtons").live('click', function() {
nextimage=$(this).attr("alt");
$("#extraImageButtons img.galleryButtons").attr("src","http://www.steveszone.co.uk/images/button_sets/pink_square_button1n.png");
$(this).attr("src","http://www.steveszone.co.uk/images/button_sets/black_square_button1n.png");
clearTimeout(t);
doSlideshow();
});
});
html:
<div class="homeLeadContent" style="width:965px;">
</div>
<div id="extraImageButtons"></div>
Two changes make it work better for me:
Down in the "extra image buttons" handler, you call "clearInterval()" but that should be changed to "clearTimeout()".
I added another call to "clearTimeout(t)" in the "runSlides()" function right before it sets up another timeout.
Clicking on the big "CLICK ME" button might still do weird things.
edit — well here is my fork of the original jsfiddle and I think it's doing the right thing. In addition to calling "clearTimeout()" properly, I also changed the code in "doSlideshow()" so that it empties out the content <div> before adding another image.