Reputation: 1165
I am trying to make a function which everytime a user scrolls "into" some container div, the web smoothly scrolls to that div (I don't know if I am being clear, you can undertand it better looking at my JSFiddle)
It works fine when the user scrolls slowly or using arrow keys, but if the user makes an abrupt scroll then the animated scrolling is not smooth at all. It also sometimes makes a jump on the opposite direction in the middle of the animation.
I am uncertain if this is a cause of momentum scrolling or the increase in scroll speed when user spins the scrollwheel very fast. Or maybe some other reason I didn't think of.
Here is the minimal code to reproduce the bug (same as in JSFiddle)
HTML:
<body>
<div style="background-color:red;" class="autoscroll">
<p style="line-height:3;color:white;">
Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem
</p>
</div>
<div style="background-color:blue;" class="autoscroll">
<p style="line-height:3;color:white;">
Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>
</p>
</div>
<div style="background-color:green;" class="autoscroll">
<p style="line-height:3;color:white;">
Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem
</p>
</div>
<div style="background-color:brown;" class="autoscroll">
<p style="line-height:3;color:white;">
Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>Ipsum Lorem<br><br>
</p>
</div>
</body>
CSS:
.autoscroll{
min-height:100%;
}
html, body, p{
margin:0;
padding:0;
}
JS:
$(function() {
var scrollFlag = true;
window.setTimeout(function(){
$(window).on('wheel keydown scroll',function(event){
var validate_scroll = false;
var scroll_direction = '';
if(event.type=="wheel"){
if(event.originalEvent.deltaY < 0){
scroll_direction = 'up';
validate_scroll = true;
}
if(event.originalEvent.deltaY > 0){
scroll_direction = 'down';
validate_scroll = true;
}
}else{ //keydown
if(event.which == 38){ //UP
scroll_direction = 'up';
validate_scroll = true;
}
if(event.which == 40){ //DOWN
scroll_direction = 'down';
validate_scroll = true;
}
}
if(validate_scroll && scrollFlag){
var windowHeight = $(window).height();
var st = $(window).scrollTop();
var st2 = windowHeight + st;
if (scroll_direction == 'down'){
//downscroll
$('.autoscroll').each(function(){
var ost = $(this).offset().top;
if (ost < st2 && st < ost && scrollFlag){
console.log('smooth scrolling down');
disableScroll();
scrollFlag = false;
$('window,html,body').stop(true);
$('html,body').animate({
scrollTop: ost
}, 1200, "linear", function(){
enableScroll();
scrollFlag = true;
});
}
});
}else if (scroll_direction == 'up'){
//upscroll
$('.autoscroll').each(function(){
var ost = $(this).offset().top;
var ost2 = ost + $(this).outerHeight(true);
if (ost2 < st2 && st < ost2 && scrollFlag){
console.log('smooth scrolling up');
disableScroll();
scrollFlag = false;
$('window,html,body').stop(true);
$('html,body').animate({
scrollTop: ost2 - windowHeight
}, 1200, "linear", function(){
enableScroll();
scrollFlag = true;
});
}
});
}
}//if validate_scroll && scrollFlag
});
}, 1000)
});
I also have two other functions, disableScroll
and enableScroll
. These I did not included in my JSFiddle because the bug reproduces anyway, it was just an attempt to fix it but I am lost whether I should keep or delete these functions. I would also appreciate an advice on this
var keys = {37: 1, 38: 1, 39: 1, 40: 1};
function preventDefault(e) {
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
function preventDefaultForScrollKeys(e) {
if (keys[e.keyCode]) {
preventDefault(e);
return false;
}
}
function disableScroll() {
console.log('disabling scroll')
if (window.addEventListener) // older FF
window.addEventListener('DOMMouseScroll', preventDefault, false);
window.onwheel = preventDefault; // modern standard
window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
window.ontouchmove = preventDefault; // mobile
document.onkeydown = preventDefaultForScrollKeys;
}
function enableScroll() {
console.log('enabling scroll')
if (window.removeEventListener)
window.removeEventListener('DOMMouseScroll', preventDefault, false);
window.onmousewheel = document.onmousewheel = null;
window.onwheel = null;
window.ontouchmove = null;
document.onkeydown = null;
}
I have made several research on this matter but I was not able to fix it. Any help is very much appreciated. Thank you.
Upvotes: 0
Views: 255
Reputation: 4425
Here's a different approach:
https://jsfiddle.net/aq5a43kr/5/
The code is totally rewritten. It uses timeouts and only the scroll
event so it should be working even on touch devices.
Allows fast scrolling and fixes your container padding problem because it needs only one marker <div class="as-marker"></div>
before each container. Do not needs the mousewheel plugin.
It could be easily refactored to be a jQuery plugin that can be re-used on multiple scrollable elements.
Upvotes: 1
Reputation: 4425
Your code is quite complex so my modifications could be still buggy but may can help you out or give some new ideas.
Check out my modified fiddle here
First of all, I recommend you this cross-browser jQuery mousewheel plugin: https://github.com/jquery/jquery-mousewheel
With that your event listener is only:
$(document).on('mousewheel keydown',function(event){ });
I was also using the isElementInViewport
trick from here
For checking viewport visibility of tops/bottoms of the divs I've added as-top
and as-bottom
marker elements to html like this:
<div class="autoscroll">
<div class="as-top"></div>
<p>Lipsum stuff...</p>
<div class="as-bottom"></div>
</div>
That's all from me now, hope you can use it...
Upvotes: 1