Prevent triggering a scroll event by an animated scrollTop when using jQuery - javascript

I am working on a fullscreen scrolling script. It is supposed to scroll in fixed steps, to the previous or next element, each typically occupying the full height of the page. This is a good example.
I have a scroll event callback which contains an animated scrollTop, triggering the scroll event again and getting caught in a loop. I have tried a few things such as flags, but none seem to work for me.
Here's the code:
function pageDown() {
// Some stuff, not important
if (currentIndex+1 === pageCount) nextIndex = 0;
else nextIndex = currentIndex+1;
nextPage = $pages.eq(nextIndex);
// Important stuff
$('html body').animate({
scrollTop: nextPage.offset().top
}, 400, function() {preventScroll=false});
}
function pageUp() {
// Some stuff, not important
if (currentIndex === 0) nextIndex = pageCount-1;
else nextIndex = currentIndex-1;
nextPage = $pages.eq(nextIndex);
// Important stuff
$('html body').animate({
scrollTop: nextPage.offset().top
}, 400, function() {preventScroll=false});
}
var lastScroll = 0,
preventScroll = false;
$(window).on('scroll', function() {
var currentScroll = $(window).scrollTop();
if(!preventScroll) {
preventScroll = true;
if (currentScroll > lastScroll) pageDown();
else pageUp();
}
lastScroll = currentScroll;
});

The main issue I have witnessed when testing this out is that the complete callback of jQuery's animate fires before the final scroll event it generates. Note that seems to only happen when scrolling down for some reason.
After experimenting with a 2 steps lock, where I used a flag with 3 states to cancel that final scroll event, which worked fairly well, I explored further as it was less cooperative with the rollover logic that is present in your original code (jumping to the opposite end when reaching an end).
I came up with the following code, which records the target position to be reached and ignores all scroll events as long as the current position does not match the target.
This also implements the rollover logic and must be combined with the associated HTML and CSS to work properly, as we need some blank space (a single pixel on each side here) to allow for a scroll event to be fired at the top and bottom. We also initiate a first scroll as to correctly position the first element and allow the rollover to work immediately.
I hope the comments in the code will provide the additional information necessary to understand the logic being used.
A working demo is available in this jsfiddle
HTML:
<div class="pageScroller">
<div class="bumper"></div>
<div class="page" style="background-color:red;"></div>
<div class="page" style="background-color:green;"></div>
<div class="page" style="background-color:blue;"></div>
<div class="page" style="background-color:violet;"></div>
<div class="page" style="background-color:cyan;"></div>
<div class="bumper"></div>
</div>
CSS:
html, body {
height:100%;
margin:0;
padding:0;
}
.pages {
padding:1px 0;
background-color:yellow;
}
.pageScroller, .page {
height:100%;
}
.bumper {
height:1px;
}
JavaScript:
var $pages = $('.page');
var currentIndex = 0;
var lastScroll = 0;
var currentScroll = 0;
var targetScroll = 1; // must be set to the same value as the first scroll
function doScroll(newScroll) {
$('html, body').animate({
scrollTop: newScroll
}, 400);
}
$(window).on('scroll', function() {
// get current position
currentScroll = $(window).scrollTop();
// passthrough
if(targetScroll == -1) {
// no target set, allow execution by doing nothing here
}
// still moving
else if(currentScroll != targetScroll) {
// target not reached, ignore this scroll event
return;
}
// reached target
else if(currentScroll == targetScroll) {
// update comparator for scroll direction
lastScroll = currentScroll;
// enable passthrough
targetScroll = -1;
// ignore this scroll event
return;
}
// get scroll direction
var dirUp = currentScroll > lastScroll ? false : true;
// update index
currentIndex += (dirUp ? -1 : 1);
// reached before start, jump to end
if(currentIndex < 0) {
currentIndex = $pages.length-1;
}
// reached after end, jump to start
else if(currentIndex >= $pages.length) {
currentIndex = 0;
}
// get scroll position of target
targetScroll = $pages.eq(currentIndex).offset().top;
// scroll to target
doScroll(targetScroll);
});
// scroll to first element
$(window).scrollTop(1)

Related

How to fix double animation effect when using $(window).scroll function?

I'm trying to fix an issue whereas when the user scrolls, the elements in viewport already, starts triggering the animation a second time. This only happens when the page is loaded at the very top of the page then proceeding to scroll down. When positioned anywhere else on the page, it works fine and elements animate correctly and doesn't trigger twice in-view. To reiterate, the main issue is when the <section> tags animate, it'll animate again after page load when I scroll from the very top down, sort like a flashback initially, which I don't want. How do I fix this bug?
Here's the jsfiddle: https://jsfiddle.net/TheAmazingKnight/oybta7dp/10/
Code Attempt:
I tried something like this to do a check if these elements are already in view, dont trigger the animation until it has left view and reenters, but it doesn't work.
if ($(this).isInViewport()) {
if($(this).parent().hasClass("animate-section") && $(this).parent().hasClass("animate")) {
$(this).removeClass('animate-element');
$(this).removeClass('animate');
return false; // abort function if element is already in view preventing double animation
}
}
jQuery Code:
$(document).ready(function() {
var flag = true; // var to detect if function already ran once
if (flag) {
// Set the animation to be triggered when the section comes into view
var sections = $('.animate-section');
sections.each(function() { // check which sections are in-view on page load
if ($(this).isInViewport()) {
$(this).addClass('animate'); // Add the "animate" class to trigger the animation
} else {
$(this).removeClass('animate'); // Remove the "animate" class if the section is out of view
}
});
flag = false; // change boolean var as function already ran once
}
// Set the animation to be triggered when the element comes into view
var elements = $('.animate-element');
$(window).scroll(function() {
elements.each(function() {
if ($(this).isInViewport()) {
if($(this).parent().hasClass("animate-section") && $(this).parent().hasClass("animate")) {
$(this).removeClass('animate-element');
$(this).removeClass('animate');
//return false; // abort function if element is already in view preventing double animation
}
$(this).css("visibility", "visible");
$(this).addClass('animate'); // Add the "animate" class to trigger the animation
} else {
$(this).css("visibility", "hidden");
$(this).removeClass('animate'); // Remove the "animate" class if the section is out of view
}
});
});
});
// jQuery function to check if element is visible in viewport
$.fn.isInViewport = function() {
var elementTop = $(this).offset().top;
var elementBottom = elementTop + $(this).outerHeight();
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
return elementBottom > viewportTop && elementTop < viewportBottom;
};
EDIT:
Since this double animation effect only occurs at the top of the page, I added a code hack workaround for the time being. But if anyone can figure out a resolve would be great. Thanks!
$(document).ready(function () {
// code hack to scroll ever-so-slightly to trigger animating the elements only if initially loaded at the top of page to avoid double animation effect when user scrolls
if (window.scrollY == 0) {
Scrolldown();
}
});
// function to scroll ever-so-slightly
function Scrolldown() {
window.scroll(0, 1);
}
If I got it correctly, you want the animation for all the elements in the view when the page load, and then, just for the new ones while scrolling. I played around with your code and this is what I came up with.
/* CHAT PROMPT: Add some jQuery code where as when the user scrolls through the page, the section will animate as they come into view. */
$(document).ready(function() {
$('.animate-element').addClass("animate");
// Set the animation to be triggered when the element comes into view
var elements = $('.animate-element');
$(window).scroll(function() {
elements.each(function() {
if ($(this).isInViewport()) {
$(this).css("visibility", "visible");
$(this).addClass('animate'); // Add the "animate" class to trigger the animation
} else {
$(this).css("visibility", "hidden");
$(this).removeClass('animate'); // Remove the "animate" class if the section is out of view
}
});
});
});
// jQuery function to check if element is visible in viewport
$.fn.isInViewport = function() {
var elementTop = $(this).offset().top;
var elementBottom = elementTop + $(this).outerHeight();
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
return elementBottom > viewportTop && elementTop < viewportBottom;
};
I just removed that flag logic and added the "animate" class to all the element that should be animated at loading. When the scroll function is triggered, those elements already have the "animate" class, so they won't be animated again.
Here the jfiddle: https://jsfiddle.net/r25v7qot/1/

jquery detect direction of scroll event up/down and bottom and store it to use outside of scroll function

I have the following function which I tried to make for detecting if the scroll event is up or down. It works fine but I also wanted to check if the user has reached the bottom to perform some actions. Or even better, I wanted to replace the down event with the bottom check. In simple words, it should always detect scroll up but should only detect bottom once the scrollbar reaches the bottom and not when scrolling down. Lastly, I want to store this in the variable scroll and use it later outside of the $().scroll() event. However, when I do console.log(scroll) outside of the scroll event it updates only once during the page load and never returns updated value on scroll. If I place this inside of the scroll event then it updates properly. But I need to use it outside of the scroll event and therefore it is mandatory that I can get the updated value of scroll variable.
var lastScrollTop = 0,
delta = 5,
scroll = '';
$('.chat-box').scroll(function(event) {
var st = $(this).scrollTop();
if (Math.abs(lastScrollTop - st) <= delta) {
return;
}
if (st > lastScrollTop) {
scroll = 'down';
} else {
scroll = 'up';
}
lastScrollTop = st;
console.log(scroll); // RETURNS UPDATED VALUE PROPERLY AS SCROLL EVENT TRIGGERS IT
});
console.log(scroll); // DOESN'T RETURN UPDATED VALUE
Expectations: How can I get the following?
Detect when the scrollbar hits the bottom and update the scroll variable with value bottom.
Store the updated value in the scroll variable to use it outside of the scope of $('.chat-box').scroll(function (event) event.
The console statement in the last line is executed after the script is loaded. At this time the var scroll is an empty string. After that the last line will not be executed again.
If you call the console statement after scrolling again, maybe in a function or another event handler, the updated value is loged. You could for example call that statement in a click event handler:
$('.chat-box').on('click', function() {
console.log('click: ' + scroll); // DOES NOW RETURN THE UPDATED VALUE
});
Working example:
(i changed the selected element for the scroll event to $(window) for demonstration)
var lastScrollTop = 0,
delta = 5,
scroll = '';
$(window).scroll(function(event) {
var st = $(this).scrollTop();
if (Math.abs(lastScrollTop - st) <= delta) {
return;
}
if (st > lastScrollTop) {
scroll = 'down';
} else {
scroll = 'up';
}
lastScrollTop = st;
console.log('scroll: ' + scroll); // RETURNS UPDATED VALUE PROPERLY AS SCROLL EVENT TRIGGERS IT
});
$('.chat-box').on('click', function() {
console.log('click: ' + scroll); // DOES NOW RETURN THE UPDATED VALUE
});
.chat-box {
width: 300px;
height: 1000px;
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="chat-box"></div>

How to trigger automatic page scroll on a particular page position with sound

I am trying to create a interactive web comic (in html, css and javascript) where I want to trigger an automatic page scroll down at a defined speed from one point to another point, to make a sequential animation using multiple jpg images.
In simple words, when a reader scrolls and reaches a certain (my already defined) position of a page, the page automatically force scrolls the page further down at a pre-defined point.
Exactly like they did in this web toon: http://comic.naver.com/webtoon/detail.nhn?titleId=350217&no=31
I also want to trigger the sound effects, just like they did in the above mentioned link...
I tried to accomplish this using the following script, but was unable to control the start and stop position for the scroll. Plus I also want it to scroll only once on a single page load.
<SCRIPT language=JavaScript1.2>
//change 1 to another integer to alter the scroll speed. Greater is faster
var speed=1
var currentpos=0,alt=1,curpos1=0,curpos2=-1
function initialize(){
startit()
}
function scrollwindow(){
if (document.all &&
!document.getElementById)
temp=document.body.scrollTop
else
temp=window.pageYOffset
if (alt==0)
alt=2
else
alt=1
if (alt==0)
curpos1=temp
else
curpos2=temp
if (curpos1!=curpos2){
if (document.all)
currentpos=document.body.scrollTop+speed
else
currentpos=window.pageYOffset+speed
window.scroll(0,currentpos)
}
else{
currentpos=0
window.scroll(0,currentpos)
}
}
function startit(){
setInterval("scrollwindow()",50)
}
window.onload=initialize
</SCRIPT>
Thanks in advance
Here is a basic auto scroll function which will trigger when the user scrolls past 300px.
var scroll = true;
$(window).scroll(function () {
var position = $(document).scrollTop();
console.log(position);
if(position > 300 && position < 400 && scroll==true ) {
scroll = false;
$('html,body').animate({
scrollTop: $("#scrollTo").offset().top
},2000);
}
})
EDIT
At the moment it is a once time thing, however if you add this if statement after the first if statement it will reset the scroll state to true and will allow it to run again.
if (position > 0 && position < 300 && scroll==false) {
scroll = true;
}
EDIT
To get sound to play you can use the new the .play command and just add it to what happens in the IF statement, here is an example of the .play code;
$('#videoId').get(0).play();
EDIT
Here is the code working not by setting a position but it finding the divs position - here is it working in JSFiddle - https://jsfiddle.net/3fxcbs2k/3/
var scroll = true;
$(window).scroll(function (e) {
var position = $(document).scrollTop();
var startP = $("#scrollTOO").position();
var finishP = $("#scrollTo");
if(position > startP.top && startP.top+100 && scroll==true ) {
scroll = false;
$('html,body').animate({scrollTop: finishP.offset().top},2000);
} if (position > 0 && position < 300 && scroll==false) {
scroll = true;
}
})
To set the position you want it to start scrolling change the value of
var startP = $(" ").position();
Then to set the finish position change the value of
var finsihP = $(" ");
Audio
<audio id="sound">
<source src="sound.mp3" type="audio/mpeg">
</audio>
var scroll = true;
$(window).scroll(function (e) {
var position = $(document).scrollTop();
var startP = $("#scrollTOO").position();
var finishP = $("#scrollTo");
Jquery play looks like - $('#sound').get(0).play();
So just add it to the what happens when triggered, like below.
if(position > startP.top && startP.top+100 && scroll==true ) {
scroll = false;
$('html,body').animate({scrollTop: finishP.offset().top},2000);
$('#sound').get(0).play();
} if (position > 0 && position < 300 && scroll==false) {
scroll = true;
}
})
To make more do the following
Think of the var as id tags which can be called upon later in the code and after the = is there values for example;
var startP = $("#scrollTOO").position();
startP is the vars unique name, and its value is the div with the id of scrollTOO position.
So to make more start and end points you will have to make more vars 1 start and one end point. Example;
var startP = $("#scrollTOO").position();
var finishP = $("#scrollTo");
var startP2 = $("#point2").position();
var finishP2 = $("#pint2");
and then its just to make another if statement, the same as before but replace the start points and end points,
if(position > startP2.top && startP2.top+100 && scroll==true ) {
scroll = false;
$('html,body').animate({scrollTop: finishP2.offset().top},2000);
However because we have it so it will only trigger once will have to make more var scrolls, 1 for each one.
var scroll = true;
var scroll2 = true;
var scroll3 = true;
var scroll4 = true;
and then each if statement will have to look for the relevant scroll for example the second scroll if statement will look like.
if(position > startP2.top && startP2.top+100 && scroll2==true ) {
scroll2 = false;

Enabling/triggering mousewheel effect ONLY on certain div (not fullpage)

I'm writing this after searching for mousewheel events in jQuery, but perhaps I'm not asking the right questions due to my lack of knowledge, and that's why I'm not finding any useful answers yet.
What I would like to achieve is a mousewheel effect that I can trigger only inside a certain div called #scroller. I'm using the jquery mousewheel plugin by Brandon Aaron and a script that updates the top value to the next or previous .js-slide whenever I delta scroll.
FIDDLE LINK:
I created this fiddle link. As you can see, it "jumps" from slide to slide, but then the content outside #scroller is not accesible anymore! I would like it to have a normal wheelmouse behaviour :S. I also have a working url where I would like to apply this effect, if you think that's of any use.
To better explain the structure and desired effect, here's an image:
I have already tried bounding my script only to $('#scroller').mouseover(function(){ my script }); but that didn't work. The mousewheel started out ok, it switched into jumping mode ok, but it never went back to normal after leaving the div #scroller and I don't find how to reset this behaviour.
My script right now is this:
jQuery(document).ready(function($) {
var slide = $('.js-slide');
var sectionHeight = $(window).height();
var slideHeight = $(slide).height();
var scrollingScreen = false;
$('#scroller').mouseover(function(){
$(slide).mousewheel(function(event, delta) {
if ( !scrollingScreen ) {
scrollingScreen = true; // prevent call
var top = $("body").scrollTop() || $("html").scrollTop();
// Chrome places overflow at body, Firefox places whacks at html...
// Finds slide headers above/below the current scroll top
var candidates = $(slide).filter(function() {
if ( delta < 0 )
return $(this).offset().top > top + (1);
else
return $(this).offset().top < top - (1);
});
// one or more slides found? Update top
if ( candidates.length > 0 ) {
if ( delta < 0 )
top = candidates.first().offset().top;
else if ( delta > 0 )
top = candidates.last().offset().top;
}
// Perform animated scroll to the right place
$("html,body").animate({ scrollTop:top }, "easeInOutQuint", function() {
scrollingScreen = false; // Release call
});
}
return false;
}); // closes mousewheel
}); // closes mouseover
});
Any help or insight on how to achieve this would be greatly appreciated.
Thank you!
Ok. Finally I found it!! I reviewed the web where the plugin author records different mousewheel events, including deactivating all of them and reseting a normal scrolling mouse. There's where I found the use of the function .unmousewheel(), just what I wanted!
But now, as the script is not able to find further slides past de last when scrolling down, and before the first when scrolling up, it became impossible to access content before and after #scroller with the scrolling wheel. That's why I had to change a bit the script and force a jump while on the first slide or the last.
Anyway, here's the script:
jQuery(document).ready(function($) {
var slide = $('#scroller .sectioncontainer');
var sectionHeight = $(window).height();
var slideHeight = slide.height();
var scrollingScreen = false;
slide.mousewheel(function(event, delta) {
if ( !scrollingScreen ) {
scrollingScreen = true; // prevent call
var top = $("body").scrollTop() || $("html").scrollTop();
// Chrome places overflow at body, Firefox places whacks at html...
// Finds slide headers above/below the current scroll top
var candidates = slide.filter(function() {
if ( delta < 0 )
return $(this).offset().top > top + (1/120);
else
return $(this).offset().top < top - (1/120);
});
// one or more slides found? Update top
if ( candidates.length > 0 ) {
if ( delta < 0 )
top = candidates.first().offset().top;
else if ( delta > 0 )
top = candidates.last().offset().top;
} else{ // no more slides found
if ( delta < 0 )
top = $("#contact").offset().top;
else if ( delta > 0 )
top = $("#about").offset().top;
}
// Perform animated scroll to the right place
$("html,body").animate({ scrollTop:top }, "easeInOutQuint", function() {
scrollingScreen = false; // Release call
});
}
return false;
});
$("#contact").unmousewheel();
$("#about").unmousewheel();
$("#div1").unmousewheel();
$("#div2").unmousewheel();
$("#div3").unmousewheel();
$("#div4").unmousewheel();
$("#div5").unmousewheel();
// . . .
//and all other divs and sections that don't use the mousewheel
});
And here's the result.

jQuery - stop autoscrolling div when hovered

This is a follow-up post to a previous question: jQuery - scroll down every x seconds, then scroll to the top
I have refined the scrip a little further, but am having a little trouble with the last step.
I have a div that automatically 50px at a time until it reaches the bottom, at which point it scrolls to the top and starts again. I have this working perfectly thanks to the above question and with a little add work.
I need to make all scrolling stop when the div is hovered. I have done part of this already (there is no incremental scrolling down on hover) but I cannot get the full picture. The div will still scroll to the top even when hovered.
Here is my jQuery and a fiddle to go along with it: http://jsfiddle.net/wR5FY/1/
var scrollingUp = 0;
var dontScroll = 0;
window.setInterval(scrollit, 3000);
function scrollit() {
if(scrollingUp == 0 && dontScroll == 0) {
$('#scroller').animate({ scrollTop: $("#scroller").scrollTop() + 50 }, 'slow');
}
}
$('#scroller').bind('scroll', function () {
if (dontScroll == 0) {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
scrollingUp = 1;
$('#scroller').delay(2000).animate({ scrollTop: 0 }, 1000, function() {
scrollingUp = 0;
});
}
}
});
$('#scroller').bind('mouseenter', function() {
dontScroll = 1;
});
$('#scroller').bind('mouseleave', function() {
dontScroll = 0;
});
​
In the fiddle, try hovering the scroller div when the yellow square is visible. You will see that it scrolls to the top.
A couple of notes:
You will notice I have used mouseenter and mouseleave rather than hover and mouseout. This was the best way I could find to ensure all child elements within the div didn't have an adverse affect.
A potential problem area is the fact that I have binded to the scroll event for my function that scrolls to the top. I think this might cause some additional problems when a user is manually scrolling through the items, with my jQuery trying to scroll against the user.
I did a little experimenting with killing setInterval, but I didn't find this to be very helpful as the function that triggers isn't the problem area.
My overall goal here is to lock down all automatic scrolling when a user is hovering or manually scrolling through the list. This is 90% there. If they happen to scroll to the bottom, NOTHING should happen until they move the mouse elsewhere - this is the problem.
Keep it easier ;)
The problem was that you first evaluate wheter dontScroll is zero, then start the timer.
When the timer has ended, it doesnt evaluate anymore, whether dontScroll STILL is zero.
Just pulled that into your scrollIt function:
var scrollingUp = 0;
var dontScroll = 0;
window.setInterval(scrollit, 2000);
function scrollit() {
if(dontScroll == 0){
if ($('#scroller').scrollTop() + $('#scroller').innerHeight() >= $('#scroller')[0].scrollHeight) {
scrollingUp = 1;
$('#scroller').animate({ scrollTop: 0 }, 1000, function() {
scrollingUp = 0;
});
} else if(scrollingUp == 0) {
$('#scroller').animate({ scrollTop: $("#scroller").scrollTop() + 50 }, 'slow');
}
}
}
$('#scroller').bind('mouseenter', function() {
dontScroll = 1;
});
$('#scroller').bind('mouseleave', function() {
dontScroll = 0;
});

Categories

Resources