Paralax-style scrolling, user can scroll only one slide - javascript

I am using ScrollTo plugin to create a simple paralax-style page-changing. Here is what happens when you scroll
var location = window.location.hash;
if(location == '') { var location = '#home' } // Default location
slides = ['#home', '#about', '#characters']; // All the slides in an array
// Calculating previous and next slides
var currentEl = slides.indexOf(location);
var prevEl = currentEl - 1;
var nextEl = currentEl + 1;
if(delta > 0)
{
if(location != '#home')
{
gofor(slides[prevEl]); // Scrolling up
}
}
else
{
if(location != '#characters')
{
gofor(slides[nextEl]); // Scrolling down
}
}
});
And here is the gofor function itself.
function gofor(slide)
{
$(slide).clearQueue().ScrollTo({duration: 700}); // Scrolling to the given slide
$('.nav>a').removeClass('active'); // Removing all active classes
$(slide + '_nav').addClass('active'); // Updating the menu class with the _nav suffix id
window.location.hash = slide; // Updating the location.hash with the new position
}
This is working okay but when you scroll your wheel more than one step it goes more than one page forward. How can i stop the scrolling function for a certain amount of time (like 3 seconds) and then let the mouse regain its scrolling function again?
Thanks.

Related

Pause at top and bottom continuous vertical scroll HTML

I have an embedded HTML page containing a single table of event results. I have found the code below which perfectly gives a continuous scroll, however I which to be able to pause for say 10 seconds at both the top and bottom before continuing.
How do I intercept the top and bottom and insert the pause?
Any suggestions would be much appreciated.
/*
Advanced window scroller script-
By JavaScript Kit (www.javascriptkit.com)
Over 200+ free JavaScripts here!
*/
var currentpos = 0, alt = 1, curpos1 = 0, curpos2 = -1
function initialize() {
startit()
}
function scrollwindow() {
if (document.all)
temp = document.body.scrollTop
else
temp = window.pageYOffset
if (alt == 0)
alt = 1
else
alt = 0
if (alt == 0)
curpos1 = temp
else
curpos2 = temp
if (curpos1 != curpos2) {
if (document.all)
currentpos = document.body.scrollTop + 300
else
currentpos = window.pageYOffset + 1
window.scroll(0, currentpos)
}
else {
currentpos = 0
window.scroll(0, currentpos)
}
}
function startit() {
setInterval("scrollwindow()", 25)
}
window.onload = initialize
Since setInterval is a basic javascript function, which runs a function every n-seconds, you set a global variable to capture that "interval", then cancel it when the user performs some action (click on a button). Then resubmit the setInterval to start running in the future, or tie it back to another onClick event. An example would be to have a button that will toggle the setInterval on and off, which will act as a pause/run.
Below is an example of how you could set this up. I have not tested it, so there may be some use cases that could cause issues.
var myInterval = null;
function startit() {
myInterval = setInterval("scrollwindow()", 25)
}
Then to cancel the interval:
function stopInterval() {
clearInterval(myInterval);
myInterval = null;
}
Example of how to toggle the interval on and off:
function toggleInterval() {
if ( myInterval ) {
stopInterval();
}
else {
startit();
}
}
Then just add it to any html element, or a button. This example is assuming the css is locked at the top of the scrollable pane so the user could always have access to click it.
<span onClick="toggleInterval()">Click to pause/continue scrolling.</span>
<button onClick="toggleInterval()">Pause/Scroll</button>

addEventListener not working as intended in a slideshow

My slider starts from the startValue then goes to displayValue and then goes to end value i.e 100% to 0% to -100% left. Once the transition to left is complete, I want the slide to become invisible, then go to start position and become visible again so it can again go through all of the cycle. However, my add event listener not working properly. I have market the relevant portion of the code.
JS Fiddle link: https://jsfiddle.net/ajaybirbal/zy0pq7uc/15/
I have variables that I have initialized before for my slider that I have explained below.
animationType = "left"
startValue = "-100%"
displayValue = "0%"
endValue = "-100%"
currentSlide is a int that measures what current slide is to be shown.
My part of the code that starts my slider is here and I have explained variables in commont: -
/**
* Initializes all slides to start position except the first slide for first time running
* or running after the slider is reset.
*/
function setSlidesPosition(startPos = 1, maxPos = numberOfSlides){
for ( let index = startPos; index < maxPos; index++){
slides[index].style[animationType] = startValue;
}
}
/***
* Starts the slideshow.
*/
function startSlideshow(){
setSlidesPosition();
setInterval(() => {
//Is the slideshow loaded for the first time
//was there a reset done? //true or false
if (firstTimeLoaded){
currentSlide = 0;
firstTimeLoaded = false;
}
//Display the current slide
//numberOfSlides is total number of slides in slideshow
if (currentSlide === (numberOfSlides - 1)){ //If it is the last slide
slides[0].style[animationType] = displayValue;
} else {
slides[currentSlide + 1].style[animationType] = displayValue;
}
//Make slide that just displayed go to the end position
slides[currentSlide].style[animationType] = endValue;
//--------------Issue here------------------------
//I want slide to go to starting position as soon as transition to end location is complete
slides[currentSlide].addEventListener('transitionend', () => {
slides[currentSlide].style.visibility = "none";
slides[currentSlide].style[animationType] = startValue;
slides[currentSlide].addEventListener('transitionend', () => {
slides[currentSlide].style.visibility = "visible";
})
})
//reset the counter if last slide is reached else increase the counter
if (currentSlide === (numberOfSlides - 1)){
currentSlide = 0;
setSlidesPosition();
} else {
currentSlide++;
}
}, duration);
}
Why isn't my add event listener being called properly? My slides go invisible midway.

Sidescrolling jittery when changing directions

I made my own sidescrolling parallax using jQuery and the mousewheel plugin. It's working great so far except for the fact that when I change directions of the scroll, it jitters first before actually scrolling (as though it's scrolling one unit to the previous side before actually moving to the correct one). I've tried adding a handler for this which supposedly stops the scroll completely.
Here is my script so far:
var scroll = 0; // Where the page is supposed to be
var curr = scroll; // The current location while scrolling
var isScrolling = false; // Tracker to check if scrolling
var previous = 0; // Tracks which direction it was previously
var loop // setInterval variable
function parallax() {
isScrolling = true;
// Loops until it's where it's supposed to be
loop = setInterval(() => {
if ( curr - scroll == 0 ) {
clearInterval(loop);
isScrolling = false;
return;
}
// Move the individual layers
$('.layer').each(function() {
$(this).css('left', -curr * $(this).data("speed"));
});
// Add/subtract to current to get closer to where it's supposed to be
curr += (scroll < curr) ? -0.5 : 0.5;
}, 25);
}
$(document).ready(() => {
$('.scrolling-container').mousewheel((e, dir) => {
e.preventDefault();
// If the speed's magnitude is greater than 1, revert it back to 1
if ( Math.abs(dir) > 1) {
dir = Math.sign(dir);
}
// If the direction changes, stop the current animation then set scroll to where it currently is
if ( previous !== dir ) {
previous = dir;
isScrolling = false;
scroll = curr;
clearInterval(loop);
}
// If not at the left most scrolling to the left, add to scroll
if ( scroll - dir >= 0 ) scroll -= dir;
// Call parallax function if it's not yet running
if ( !isScrolling ) {
parallax();
}
})
})
I think it's easier to show so here's a codepen of the functional parts: https://codepen.io/ulyzses/pen/yLyoYZm
Try scrolling for a while then change direction, the jittery behaviour should be noticeable.

Is it possible to determine where a scroll will end up using javascript? If so, how?

I have a situation where, for example, if a user's scroll will result in a 1000 px change in scrollTop I'd like to know ahead of time.
The perfect example is iCalendar's control over a user's scroll. No matter how hard you scroll in the iCalendar application, the farthest you can scroll is to the next or previous month.
I currently have a very hackish solution to limit scroll behavior, which only takes into account where the user's scroll currently is.
MyConstructor.prototype._stopScroll = function(){
//Cache the previous scroll position and set a flag that will control
//whether or not we stop the scroll
var previous = this._container.scrollTop;
var flag = true;
//Add an event listener that stops the scroll if the flag is set to true
this._container.addEventListener('scroll', function stop(){
if(flag) {
this._container.scrollTop = previous;
}
}.bind(this), false);
//Return a function that has access to the stop function and can remove it
//as an event listener
return function(){
setTimeout(function(){
flag = false;
this._container.removeEventListener('scroll', stop, false);
}.bind(this), 0);
}.bind(this);
};
This approach works, and will stop a scroll in progress, but it is not smooth and I'd love to know if there's a better way to accomplish this.
The key to this question is can I know ahead of time where a scroll will end up. Thanks!!!
Edit: Just found the following project on github:
https://github.com/jquery/jquery-mousewheel
I tried the demo and it's able to report my touchpad and mouse scroll speed. Also it able to stop scrolling without any position fixed hacks :D
I'll have a look in the next few days and see if I can write anything that reports scroll speed, direction, velocity, device etc. Hopefully I'm able to make some jquery plugin that can override all scrolling interaction.
I'll update this post when I've got more info on this subject.
It's impossible to predict where a mouse scroll will end up.
A touchscreen/touchpad swipe on the other hand has a certain speed that will slow down after the user stopped swiping, like a car that got a push and starts slowing down afterwards.
Sadly every browser/os/driver/touchscreen/touchpad/etc has it's own implementation for that slowing down part so we can't predict that.
But we can of course write our own implementation.
We got 3 implementations that could be made:
A. Direction
B. Direction and speed
C. Direction, speed and velocity
iCalender probably uses implementation A.
Implementation A:
Outputs scroll direction to console, user is able to scroll +/- 1px
before the direction is detected.
Demo on JSFiddle
Demo with animation on JSFiddle
(function iDirection() {
var preventLoop = true;
var currentScroll = scrollTop();
function scroll() {
if(preventLoop) {
//Get new scroll position
var newScroll = scrollTop();
//Stop scrolling
preventLoop = false;
freeze(newScroll);
//Check direction
if(newScroll > currentScroll) {
console.log("scrolling down");
//scroll down animation here
} else {
console.log("scrolling up");
//scroll up animation here
}
/*
Time in milliseconds the scrolling is disabled,
in most cases this is equal to the time the animation takes
*/
setTimeout(function() {
//Update scroll position
currentScroll = newScroll;
//Enable scrolling
unfreeze();
/*
Wait 100ms before enabling the direction function again
(to prevent a loop from occuring).
*/
setTimeout(function() {
preventLoop = true;
}, 100);
}, 1000);
}
}
$(window).on("scroll", scroll);
})();
Implementation B:
Outputs scroll direction, distance and average speed to console, user is able to scroll the amount of pixels set in the distance variable.
If the user scrolls fast they might scroll a few more pixels though.
Demo on JSFiddle
(function iDirectionSpeed() {
var distance = 50; //pixels to scroll to determine speed
var preventLoop = true;
var currentScroll = scrollTop();
var currentDate = false;
function scroll() {
if(preventLoop) {
//Set date on scroll
if(!currentDate) {
currentDate = new Date();
}
//Get new scroll position
var newScroll = scrollTop();
var scrolledDistance = Math.abs(currentScroll - newScroll);
//User scrolled `distance` px or scrolled to the top/bottom
if(scrolledDistance >= distance || !newScroll || newScroll == scrollHeight()) {
//Stop scrolling
preventLoop = false;
freeze(newScroll);
//Get new date
var newDate = new Date();
//Calculate time
var time = newDate.getTime() - currentDate.getTime();
//Output speed
console.log("average speed: "+scrolledDistance+"px in "+time+"ms");
/*
To calculate the animation duration in ms:
x: time
y: scrolledDistance
z: distance you're going to animate
animation duration = z / y * x
*/
//Check direction
if(newScroll > currentScroll) {
console.log("scrolling down");
//scroll down animation here
} else {
console.log("scrolling up");
//scroll up animation here
}
/*
Time in milliseconds the scrolling is disabled,
in most cases this is equal to the time the animation takes
*/
setTimeout(function() {
//Update scroll position
currentScroll = newScroll;
//Unset date
currentDate = false;
//Enable scrolling
unfreeze();
/*
Wait 100ms before enabling the direction function again
(to prevent a loop from occuring).
*/
setTimeout(function() {
preventLoop = true;
}, 100);
}, 1000);
}
}
}
$(window).on("scroll", scroll);
})();
Implementation C:
Outputs scroll direction, distance and speeds to console, user is able to scroll the amount of pixels set in the distance variable.
If the user scrolls fast they might scroll a few more pixels though.
Demo on JSFiddle
(function iDirectionSpeedVelocity() {
var distance = 100; //pixels to scroll to determine speed
var preventLoop = true;
var currentScroll = [];
var currentDate = [];
function scroll() {
if(preventLoop) {
//Set date on scroll
currentDate.push(new Date());
//Set scrollTop on scroll
currentScroll.push(scrollTop());
var lastDate = currentDate[currentDate.length - 1];
var lastScroll = currentScroll[currentScroll.length - 1];
//User scrolled `distance` px or scrolled to the top/bottom
if(Math.abs(currentScroll[0] - lastScroll) >= distance || !lastScroll || lastScroll == scrollHeight()) {
//Stop scrolling
preventLoop = false;
freeze(currentScroll[currentScroll.length - 1]);
//Total time
console.log("Time: "+(lastDate.getTime() - currentDate[0].getTime())+"ms");
//Total distance
console.log("Distance: "+Math.abs(lastScroll - currentScroll[0])+"px");
/*
Calculate speeds between every registered scroll
(speed is described in milliseconds per pixel)
*/
var speeds = [];
for(var x = 0; x < currentScroll.length - 1; x++) {
var time = currentDate[x + 1].getTime() - currentDate[x].getTime();
var offset = Math.abs(currentScroll[x - 1] - currentScroll[x]);
if(offset) {
var speed = time / offset;
speeds.push(speed);
}
}
//Output array of registered speeds (milliseconds per pixel)
console.log("speeds (milliseconds per pixel):");
console.log(speeds);
/*
We can use the array of speeds to check if the speed is increasing
or decreasing between the first and last half as example
*/
var half = Math.round(speeds.length / 2);
var equal = half == speeds.length ? 0 : 1;
var firstHalfSpeed = 0;
for(var x = 0; x < half; x++ ) {
firstHalfSpeed += speeds[x];
}
firstHalfSpeed /= half;
var secondHalfSpeed = 0;
for(var x = half - equal; x < speeds.length; x++ ) {
secondHalfSpeed += speeds[x];
}
secondHalfSpeed /= half;
console.log("average first half speed: "+firstHalfSpeed+"ms per px");
console.log("average second half speed: "+secondHalfSpeed+"ms per px");
if(firstHalfSpeed < secondHalfSpeed) {
console.log("conclusion: speed is decreasing");
} else {
console.log("conclusion: speed is increasing");
}
//Check direction
if(lastScroll > currentScroll[0]) {
console.log("scrolling down");
//scroll down animation here
} else {
console.log("scrolling up");
//scroll up animation here
}
/*
Time in milliseconds the scrolling is disabled,
in most cases this is equal to the time the animation takes
*/
setTimeout(function() {
//Unset scroll positions
currentScroll = [];
//Unset dates
currentDate = [];
//Enable scrolling
unfreeze();
/*
Wait 100ms before enabling the direction function again
(to prevent a loop from occuring).
*/
setTimeout(function() {
preventLoop = true;
}, 100);
}, 2000);
}
}
}
$(window).on("scroll", scroll);
})();
Helper functions used in above implementations:
//Source: https://github.com/seahorsepip/jPopup
function freeze(top) {
if(window.innerWidth > document.documentElement.clientWidth) {
$("html").css("overflow-y", "scroll");
}
$("html").css({"width": "100%", "height": "100%", "position": "fixed", "top": -top});
}
function unfreeze() {
$("html").css("position", "static");
$("html, body").scrollTop(-parseInt($("html").css("top")));
$("html").css({"position": "", "width": "", "height": "", "top": "", "overflow-y": ""});
}
function scrollTop() {
return $("html").scrollTop() ? $("html").scrollTop() : $("body").scrollTop();
}
function scrollHeight() {
return $("html")[0].scrollHeight ? $("html")[0].scrollHeight : $("body")[0].scrollHeight;
}
Just had a look at scrollify mentioned in the comments, it's 10kb and needs to hook at every simple event: touch, mouse scroll, keyboard buttons etc.
That doesn't seem very future proof, who know what possible user interaction can cause a scroll in the future?
The onscroll event on the other hand will always be triggered when the page scrolls, so let's just hook the animation code on that without worrying about any input device interaction.
As #seahorsepip states, it is not generally possible to know where a scroll will end up without adding custom behavior with JavaScript. The MDN docs do not list any way to access queued scroll events: https://developer.mozilla.org/en-US/docs/Web/Events/scroll
I found this information helpful:
Normalizing mousewheel speed across browsers
It highlights the difficulty of knowing where the page will go based on user input. My suggestion is to trigger a scroll to Y event when the code predicts the threshold is reached. In your example, if the scroll has moved the page 800 of 1000 pixels in a time window of 250ms, then set the scroll to that 1000 pixel mark and cut off the scroll for 500ms.
https://developer.mozilla.org/en-US/docs/Web/API/window/scrollTo
i'm not pretty sure if i've got what you're looking for. I've had project once, where i had to control the scrolling. Back then i've overwritten the default scroll event, after that you can set a custom distance for "one" scroll. Additionally added jQuery animations to scroll to a specific position.
Here you can take a look: http://c-k.co/zw1/
If that's what you're looking for you can contact me, and i'll see how much i still understand of my own thingy there
is easy to use event listener to do it. Here is a React example:
/**
* scroll promise
*/
const scrollPromiseCallback = useCallback((func:Function) => {
return new Promise((resolve, reject) => {
func(resolve, reject)
})
}, [])
/**
* scroll callback
*/
const scrollCallback = useCallback((scrollContainer, onScrollEnd, resolve) => {
/** 防抖时间 */
const debounceTime = 200
/** 防抖计时器 */
let timer = null
const listener = () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
scrollContainer.removeEventListener('scroll', listener)
resolve(true)
onScrollEnd?.()
}, debounceTime)
}
scrollContainer.addEventListener('scroll', listener)
}, [])
const scrollTo = useCallback((props:IUseScrollToProps) => {
return scrollPromiseCallback((resolve, reject) => {
const {
scrollContainer = window, top = 0, left = 0, behavior = 'auto',
} = props
scrollCallback(scrollContainer, props?.onScrollEnd, resolve)
scrollContainer.scrollTo({
top,
left,
behavior,
})
})
}, [scrollCallback, scrollPromiseCallback])

jQuery: How to properly pause a recursion?

I know there are a lot of questions on the topic, but it seems none of the answers works for me (or maybe I don't see something obvious). I am building a featured content slider that should pause on hover. However, if I move over with the mouse 5-6 times, it goes through the loop 5-6 times at once and it becomes buggy. The previous recursion doesn't stop and a new one is initiated too.
My function:
gravityFeatured.prototype.loop = function(slide) {
// Begin
var self = this;
if(typeof slide == 'undefined') {
slide = 0;
}
self.slidesWrapper.find('.slide-wrapper').removeClass('current next prev');
self.navigation.find('.navigation-item').removeClass('current');
// Current slide
currentSlide = self.slidesWrapper.find('.slide-wrapper[data-slide="' + slide + '"]');
currentNav = self.navigation.find('.navigation-item[data-slide="' + slide + '"]');
// Next slide
var nextSlide = self.slidesWrapper.find('.slide-wrapper[data-slide="' + (slide + 1) + '"]');
var next = slide+1;
if(!nextSlide.length) {
nextSlide = self.slidesWrapper.find('.slide-wrapper[data-slide="0"]');
next = 0;
}
// Prev slide
var prevSlide = self.slidesWrapper.find('.slide-wrapper[data-slide="' + (slide - 1) + '"]');
if(!prevSlide.length) {
prevSlide = self.slidesWrapper.find('.slide-wrapper[data-slide="' + (self.slides.length - 1) + '"]');
}
// Assign classes
currentSlide.addClass('current');
currentNav.addClass('current');
nextSlide.addClass('next');
prevSlide.addClass('prev');
self.scrollNavigation(slide);
// Loop
var timeout = setTimeout(function(){
self.loop(next);
}, self.options['delay']);
self.slidesWrapper.hover(function(){
clearTimeout(timeout);
}, function(){
timeout = setTimeout(function(){self.loop(next);});
});
}
So with the current setup every time you mouseout you trigger a move. Thus in and out 5 times is going to trigger 5 moves. Perhaps better behavious is to pause the countdown while the mouse is in the hovering. This would look something like:
gravityFeatured.countdown = null,
gravityFeatured.isPaused = false,
gravityFeatured.prototype.loop = function(slide) {
...
self.scrollNavigation(slide);
self.countdown = self.options['delay'] * 1000;
var timeout = null; /* need this in loopCheck */
var loopCheck = function() {
if (!this.isPaused)
this.countdown -= 500;
if (this.countdown <= 0)
this.loop(next);
else
timeout = setTimeout(loopCheck,500); /* check every half sec */
}
timeout = setTimeout(loopCheck,500);
self.slidesWrapper.hover(function(){
self.isPaused = true
}, function(){
self.isPaused = false;
});
}
A half second timeout might be a little long, could probably go down to 250ms if necessary. Should handle lots of mouse movement okay though.

Categories

Resources