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
Related
I am trying to set scroll for a web page but it's not working out properly.
What I want is that when someone scroll to the middle of the or at least a little up before the footer element so I want to trigger scroll event and then wait for like 10 to 15 seconds as on triggering the scroll event the data is loaded through ajax which takes time and then each time to do so when someone goes down again to the footer so again the scroll function should get triggered.
What I am working now with is as follows but I want to enhance it to the above requirements :
$(document).on('scroll', function() {
if($(this).scrollTop()>=$('footer').position().top){
$('div#load_more').click();
}
});
Use $(window) instead of $(document)
// helper function does exactly what it says
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
var $canfetch = true;
$(window).on('scroll', function(e) {
var scrollHeight = $(window).scrollTop();
var scrollPosition = $(".highlight")[0].offsetTop;
// if user has reached bottom of page the sleep function for next scroll event will be fired.
if (scrollPosition-scrollHeight <= 0) {
if($canfetch) {
console.log("ajax call here");
}
$canfetch = false;
// sleep function usage
sleep(15000).then(() => {
alert("15 seconds have passed");
// return to top of page
$(window).scrollTop(0);
$canfetch = true;
});
}
});
.ok {
height:500px;
}
.highlight{
height:20px;
text-align:center;
background-color:yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="ok"></div>
<div class="highlight">Reference Point</div>
<div class="ok"></div>
I just did a jsfiddle test of your code and had following observations:
a. Assuming the footer is in a div
if you are using class='footer' then you have to use
$('.footer').position().top
If you are using id='footer' then you have to use $('#footer').position().top
b. I added console.log($(this).scrollTop() + '~~footer positon top is '+ $('.footer').position().top); and found that the condition is never met.
1671~~footer positon top is 1868 when I scrolled all the way down.
Hope this helps a bit.
Side Note: You might want to consider a cool plugin called ajax load more to achieve similar feature.
Use the below function use $(window) instead of $(document):
$(window).on('scroll', function(e) {
alert("Scrolled");
});
I have been using the JQuery Code below to handle a little bit of responsiveness for a menu on a Drupal site. In the two commented lines in the resize function, I am essentially trying to enable and disable the opposite events dependent on the screen size. My first question would be since this handler triggering would be in the resize function, would it cause any kind of significant performance hit to attempt something like this? My second question would be how? I've been trying to use the on and off functions to enable/disable those handlers as needed, but I don't think I'm getting the overall syntax correct. I figure it would be best to break the existing event handlers into functions, but have left them as is for the code example.
jQuery(document).ready(function($) {
$('.nav-toggle').click(function() {
$('#main-menu div ul:first-child').slideToggle(250);
return false;
});
if( ($(window).width() > 600) || ($(document).width() > 600) ) {
$('#main-menu li').mouseenter(function() {
$(this).children('ul').css('display', 'none').stop(true,
true).slideToggle(1).css('display',
'block').children('ul').css('display', 'none');
});
$('#main-menu li').mouseleave(function() {
$(this).children('ul').stop(true, true).fadeOut(1).css('display', 'block');
})
}
else {
$('.drop-down-toggle').click(function() {
$(this).parent().children('ul').slideToggle(500);
});
}
$(window).resize(function() {
if($(window).width() > 600) {
$('div.menu-navigation-container ul.menu').css('display','block');
$('div.menu-navigation-container ul.menu ul.menu').hide();
//**Disable dropdown click and enable mouse enter and mouse leave**
}
else{
$('div.menu-navigation-container ul.menu').hide();
//**Disable mouse enter and mouse leave but enable dropdown click**
}
});
});
Use a throttle function
function throttle (callback, limit) {
var wait = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!wait) { // If we're not waiting
callback.call(); // Execute users function
wait = true; // Prevent future invocations
setTimeout(function () { // After a period of time
wait = false; // And allow future invocations
}, limit);
}
}
}
$(window).on('resize', throttle(yourResizeFunction, 200))
Read why here: http://www.paulirish.com/2009/throttled-smartresize-jquery-event-handler/
As I said, move your event binding outside of the resize function as binding event handlers within resize/scroll is not a good idea at all as you'd bind the same event over and over for every pixel resized!.
An example would look like this:
$(document) // or you can even use 'div.menu-navigation-container' as opposed to document
.on("click", ".click", function() {})
.on("mouseenter", ".hover", function() {})
.on("mouseleave", ".hover", function() {});
$(window).resize(function() {
//A bit of breathing time when the resize event pauses. Remember, the statements within the resize will trigger for every pixel resize, otherwise.
setTimeout(function() {
if( $(window).width() > 600 ) {
$('div.menu-navigation-container ul.menu').css('display','block');
$('div.menu-navigation-container ul.menu ul.menu').hide();
//I am assuming your selector on which the events are bound to be '.menu-trigger' as you did not post any HTML. Replace this with the appropriate selector.
$(".menu-trigger").removeClass("click").addClass("hover");
}
else{
$('div.menu-navigation-container ul.menu').hide();
//I am assuming your selector on which the events are bound to be '.menu-trigger' as you did not post any HTML. Replace this with the appropriate selector.
$(".menu-trigger").removeClass("hover").addClass("click");
}
}, 250);
});
Hope that helps.
I'm really new to jQuery but familiar with some other languages. I recently bought a quiz type script and I'm trying to add a simple 15 second timer to each question. It's only a fun quiz, so no need to worry about users playing with the javascript to increase time etc.
Basically, if a user does not pick a question within 15 seconds, it will automatically go on to the next question and the timer starts over again.
Answers have the .next tag, and when chosen it moves onto the next question as the code below shows (hopefully).
superContainer.find('.next').click(function () {
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500)
});
return false
});
The problem i have is if i use setInterval, i don't know how i can select the appropriate div again for fade it our and fade in the next one. I've tried the below code and a few similar scrappy idea's but it doesn't work, but maybe it will give a better idea of what I'm after though.
superContainer.find('.next').click(function () {
$active_count = $count;
countInterval = setInterval(function() {
$active_count--;
if($active_count <= 0){
clearInterval(countInterval);
$active_count = $count;
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500)
});
}
$('.question-timer').html($active_count);
}, 1000);
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500)
});
return false
});
I've only been using JQuery a day or two so excuse any obvious mistakes and bad code! Let me know if you need any other code or information
This is moderately tricky for a first jQuery project.
The knack (in this solution) is to factor out a goNext function that can be called in two ways - in response to a click event and in response to a 15 second setTimeout(), not setInterval().
$(function(){
var questionTimeout = null;
function goNext($el) {
clearTimeout(questionTimeout);
var $next = $el.next();
$el.fadeOut(500, function() {
if($next.length > 0) {
$next.fadeIn(500, function() {
questionTimeout = setTimeout(function() {
goNext($next);
}, 15000);
});
}
else {
afterLastQuestion();
}
});
}
function afterLastQuestion(){
alert("last question complete");
$start.show();
}
var $superContainer = $("#superContainer").on('click', '.next', function() {
goNext($(this).closest('.slide-container'));
return false;
});
var $start = $("#start").on('click', function(){
$(this).hide();
$superContainer.find(".slide-container")
.eq(0).clone(true,true)
.prependTo(superContainer)
.find(".next").trigger('click');
return false;
});
});
DEMO
The process is started by clicking a "start" link, causing the first question to be cloned followed by a simulated click on the clone's "next" link. This ensures that the (actual) first question is treated in exactly the same way as all the others.
I also included a afterLastQuestion() function. Modify its action to do whatever is necessary after the last question is answered (or times out).
You could keep the current question in a variable, resetting it on a next click and in the timer, e.g.
var $current;
superContainer.find('.next').click(function (e) {
e.preventDefault();
$(this).parents('.slide-container').fadeOut(500, function () {
$(this).next().fadeIn(500);
$current = $(this).next();
});
});
You'll just need to set it to your first question on initialisation, and remember to reset your timer on a next click
Also, it's usually preferable to use e.preventDefault() rather than return false.
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);
}
});
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.