How to throttle function using the mousewheel plugin? - javascript

I'm using this plugin:
https://github.com/jquery/jquery-mousewheel
It's a horizontally scrolling one-page site, and the function is intended as a "scrollsnap"-- a scroll on the current section will force snap it to the next section. However, after the first "snap", it only happens after ~5 seconds, which I believe is due to the function firing too many times in a row.
function scrollSnap() {
$('.page:not(:last-child)').each(function(){
var nextTarget = $(this).next().position().left;
$(this).mousewheel(function(){
if(event.deltaY >= 50) {
$('main').animate({
scrollLeft: nextTarget
}, 700);
console.log("scrolled to: ", nextTarget);
}
});
});
$('.page:not(:first-child)').each(function(){
var prevTarget = $(this).prev().position().left;
$(this).mousewheel(function(){
if(event.deltaY <= -50) {
$('main').animate({
scrollLeft: prevTarget
}, 700);
console.log("scrolled to: ", prevTarget);
}
});
});
}
$(document).ready(function() {
scrollSnap();
});

You can throttle events like these with something called a debounce function. You can create your own or use a utility library like lodash which has it built in.

Related

jQuery "Snap To" Effect

I have a specific effect I want for a website I'm building. As you can see in this website, I want the screen to "snap to" the next section after the user scrolls, but only after (not the instant) the scroll event has fired. The reason I don't want to use a plugin like panelSnap is because I
1: Want smaller code and
2. Want the website, when viewed on mobile, to have more of the "instant snap" effect (try reducing the browser size in the website mentioned above). I know I theoretically could try combining two plugins, like panelsnap and scrollify, and activate them appropriately when the browser is a certain width, but I don't know if I want to do that... :(
So all of that said, here's the code:
var scrollTimeout = null;
var currentElem = 0;
var options = {
scrollSpeed: 1100,
selector: 'div.panels',
scrollDelay: 500,
};
$(document).ready(function() {
var $snapElems = $(options.selector);
console.log($($snapElems[currentElem]).offset().top);
function snap() {
if ($('html, body').scrollTop() >= $($snapElems[currentElem]).offset().top) {
if (currentElem < $snapElems.length-1) {
currentElem++;
}
}else{
if (currentElem > 0) {
currentElem = currentElem - 1;
}
}
$('html, body').animate({
scrollTop: $($snapElems[currentElem]).offset().top
}, options.scrollSpeed);
}
$(window).scroll(function() {
if ($(window).innerWidth() > 766) {
if (scrollTimeout) {clearTimeout(scrollTimeout);}
scrollTimeout = setTimeout(function(){snap()}, options.scrollDelay);
}else{
//I'll deal with this later
}
});
});
My problem is that every time the snap function is called, it triggers the scroll event, which throws it into a loop where the window won't stop scrolling between the first and second elements. Here's the poor, dysfunctional site: https://tcfchurch.herokuapp.com/index.html Thank for the help.
You can use a boolean to record when the scroll animation in snap is in progress and prevent your $(window).scroll() event handler from taking any action.
Here's a working example:
var scrollTimeout = null;
var currentElem = 0;
var options = {
scrollSpeed: 1100,
selector: 'div.panels',
scrollDelay: 500,
};
$(document).ready(function() {
var scrollInProgress = false;
var $snapElems = $(options.selector);
console.log($($snapElems[currentElem]).offset().top);
function snap() {
if ($('html, body').scrollTop() >= $($snapElems[currentElem]).offset().top) {
if (currentElem < $snapElems.length-1) {
currentElem++;
}
}else{
if (currentElem > 0) {
currentElem = currentElem - 1;
}
}
scrollInProgress = true;
$('html, body').animate({
scrollTop: $($snapElems[currentElem]).offset().top
}, options.scrollSpeed, 'swing', function() {
// this function is invoked when the scroll animate is complete
scrollInProgress = false;
});
}
$(window).scroll(function() {
if (scrollInProgress == false) {
if ($(window).innerWidth() > 766) {
if (scrollTimeout) {clearTimeout(scrollTimeout);}
scrollTimeout = setTimeout(function(){snap()}, options.scrollDelay);
}else{
//I'll deal with this later
}
}
});
});
The variable scrollInProgress is set to false by default. It is then set to true when the scroll animate starts. When the animate finishes, scrollInProgress is set back to false. A simple if statement at the top of your $(window).scroll() event handler prevents the handler from taking any action while the animate scroll is in progress.
Have you considered using the well known fullPage.js library for that? Check out this normal scroll example. The snap timeout is configurable through the option fitToSectionDelay.
And nothing to worry about the size... it is 7Kb Gzipped!
I know I theoretically could try combining two plugins, like panelsnap and scrollify, and activate them appropriately when the browser is a certain width, but I don't know if I want to do that
fullPage.js also provides responsiveWidth and responsiveHeight options to turn it off under certain dimensions.

Slide div on and off the page on scroll without interrupting the animation when the user continues to scroll

I am trying to smoothly slide a div on and off the page (left to right) using jQuery only. I have accomplished the task, however if you continue to scroll up or down while the animation is still going, it will interrupt it in the middle of the action causing it to hesitate. I've run into this issue before and could never figure it out without using a plugin of some sort.
I know how to accomplish this with CSS transitions, jQuery UI, greensock, etc., but I am curious if there is a way to prevent that interruption with jQuery only. I am open to a pure JavaScript solution (no jQuery) as well if there is one.
My code:
var amountScrolled = 50;
$(window).scroll(function() {
if ($(window).scrollTop() > amountScrolled) {
$('#slide').stop().animate({marginLeft:"0px"}, 500);
} else {
$('#slide').stop().animate({marginLeft: "-400px"}, 500);
}
});
Example: https://jsfiddle.net/Hysteresis/hg9cvxop/6/
This works: JSFIDDLE link
It's all about the Callback Functions!
var amountScrolled = 50;
var loopRunning = 0;
$(window).scroll(function() {
if ($(window).scrollTop() > amountScrolled){
if(loopRunning === 0){
animateSlide("0px",500);
}
} else {
if(loopRunning === 0){
animateSlide("-400px",500);
}
}
});
function animateSlide(px, time){
loopRunning = 1;
$('#slide').stop().animate({marginLeft:px}, time, function(){
loopRunning = 0;
});
}
Well, to answer your question rather than provide advice on better ways to do it, I usually handle tasks like this by assigning a temporary class to denote that something is in the process of being animated. As somebody else said, the reason for the stuttering is because the scroll function is getting called multiple times, so you keep stopping and restarting the animation.
So you can try something like this (Fiddle):
var amountScrolled = 50;
$(window).scroll(function() {
if ($('#slide').hasClass('sliding')) {
return;
}
if ($(window).scrollTop() > amountScrolled) {
$('#slide').stop().addClass('sliding').animate({marginLeft:"0px"}, 500, function() {
$(this).removeClass('sliding');
});
} else {
$('#slide').stop().addClass('sliding').animate({marginLeft: "-400px"}, 500, function() {
$(this).removeClass('sliding');
});
}
});

query two events for on

I have a page with links on top. When a link is clicked it scrolls down to a particular section. For styling purposes, I had to write a small jQuery to have to land 100px above the actual section that it scrolls to. Now I need the pixel number to change depending on the media query. Is there something wrong about how this is written? The responsive part isn't working..
function offsetAnchor() {
if(jQuery(location.hash).length !== 0) {
if (jQuery(window).width() <= 350) {
window.scrollTo(window.scrollX, window.scrollY - 180);
}
else {
window.scrollTo(window.scrollX, window.scrollY - 100);
}
}
}
// This will capture hash changes while you are on the same page
jQuery(window).on("hashchange", function () {
offsetAnchor();
});
window.setTimeout(function() {
offsetAnchor();
}, 1);
the syntax should be :
jQuery(window).on("hashchange resize", function () {}
you can read more about .on() function at jQuery API

Height check on resize causing lag

I'm using the below jquery to check and reset the height of my div on resize. However, there is a terrible freeze/lag when using the below code. The structure is just three absolutely positioned divs. One at the top, one at the bottom, and one in the middle. Any idea why this might be happening?
$(window).resize(function () {
if ($("#master_bodywrapper_div").height() < 500) {
$("#master_bodywrapper_div").height(500);
}
else {
$("#master_bodywrapper_div").height("auto");
}
});
This is because, on some browsers, the resize event can fire dozens of times per second. You should have a resizing function that is called only after the window is done being resized, like so:
(function($) {
var resizeTimer = false;
function doResize() {
if ($("#master_bodywrapper_div").height() < 500) {
$("#master_bodywrapper_div").height(500);
}
else {
$("#master_bodywrapper_div").height("auto");
}
resizeTimer = false;
}
$(window).on("resize", function() {
if (resizeTimer) {
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(doResize, 300);
});
})(jQuery);
Hope this helps!
Well, it is querying the DOM twice every time resize is triggered and this function called, which might be the source (DOM querying can be expensive). Note that resize is triggered a lot even if you simply grab the edge and move your mouse (with a moderate speed) 200px to one side. Try moving the $("#master_bodywrapper_div") query to above the resize callback, cached in a var, and then use that var reference in your callback function.
var div = $("#master_bodywrapper_div");
$(window).resize(function () {
if (div.height() < 500) {
div.height(500);
}
else {
div.height("auto");
}
});

Cancel scrolling after user interaction

My webpage animates scrolling when users click on links to the same page. I want to cancel this animation as soon as the user tries to scroll (otherwise the user and the browser are fighting for control) – no matter whether with the mouse wheel, the keyboard or the scrollbar (or any other way – are there other ways of scrolling?). I managed to cancel the animation after the mouse wheel or keyboard are used, how do I get this working with the scrollbar?
Here is how my code looks for the keyboard:
$(document.documentElement).keydown( function (event) {
if(event.keyCode == 38 || 40) stopScroll();
});
function stopScroll() {
$("html, body").stop(true, false);
}
I also tried a more elegant way of doing this by using scroll(), the problem is that scroll() catches everything including the animated and automated scrolling. I could not think of any way to let it catch all scrolling except the animated scrolling.
you need animation marker, something like this
$("html, body").stop(true, false).prop('animatedMark',0.0).animate({scrollTop : top, animatedMark: '+=1.0'})
Here is the code, the code was mix of GWT and javascript so moved it to js, not fully tested, please try it
var lastAnimatedMark=0.0;
function scrollToThis(top){
// Select/ stop any previous animation / reset the mark to 0
// and finally animate the scroll and the mark
$("html, body").stop(true, false).prop('animatedMark',0.0).
animate({scrollTop : top, animatedMark: '+=1.0'}
,10000,function(){
//We finished , nothing just clear the data
lastAnimatedMark=0.0;
$("html, body").prop('animatedMark',0.0);
});
}
//Gets the animatedMark value
function animatedMark() {
var x=$("html, body").prop('animatedMark');
if (x==undefined){
$("html, body").prop('animatedMark', 0.0);
}
x=$("html, body").prop('animatedMark');
return x;
};
//Kills the animation
function stopBodyAnimation() {
lastAnimatedMark=0;
$("html, body").stop(true, false);
}
//This should be hooked to window scroll event
function scrolled(){
//get current mark
var currentAnimatedMark=animatedMark();
//mark must be more than zero (jQuery animation is on) & but
//because last=current , this is user interaction.
if (currentAnimatedMark>0 && (lastAnimatedMark==currentAnimatedMark)) {
//During Animation but the marks are the same !
stopBodyAnimation();
return;
}
lastAnimatedMark=currentAnimatedMark;
}
Here is the blog about it
http://alaamurad.com/blog/#!canceling-jquery-animation-after-user-interaction
Enjoy!
Here's a jquery function that should do the trick:
function polite_scroll_to(val, duration, callback) {
/* scrolls body to a value, without fighting the user if they
try to scroll in the middle of the animation. */
var auto_scroll = false;
function stop_scroll() {
if (!auto_scroll) {
$("html, body").stop(true, false);
}
};
$(window).on('scroll', stop_scroll);
$("html, body").animate({
scrollTop: val
}, {
duration: duration,
step: function() {
auto_scroll = true;
$(window).one('scroll', function() {
auto_scroll = false;
});
},
complete: function() {
callback && callback();
},
always: function() {
$(window).off('scroll', stop_scroll);
}
});
};
It's not very elegant, but you could use a flag of some kind to detect what type of scrolling you're dealing with (animated or 'manual') and always kill it when it's animated. Here's an untested example:
var animatedScroll = false;
// you probably have a method looking something like this:
function animatedScrollTo(top) {
// set flag to true
animatedScroll = true;
$('html').animate({
scrollTop : top
}, 'slow', function() {
// reset flag after animation is completed
animatedScroll = false;
});
}
function stopScroll() {
if (animatedScroll) {
$("html, body").stop(true, false);
}
}

Categories

Resources