Get scrollTop when scrolling fast - javascript

I'm trying to do a specific task when the user scrolls into a particular area.
The issue I'm having is, when the user scrolls at a normal speed, I can capture the current scrollTop position, and trigger the desired function.
If, however, the user scrolls faster, scrollTop seems to only to be captured when the scrolling has slowed down back to a normal speed - meaning the user is able to completely scroll past the area where I want the function to trigger.
I need some way of being updated with every scrollTop position at every scroll speed.
I'm simply using:
$('body').scroll(function(e)
{
intScrollPoint = $(body).scrollTop();
});
Any ideas?

Throttle can solve your problem,
window.addEventListener('scroll', throttle(callback, 1));
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}
function callback() {
// do something,
}
For more details, check this link

Related

Typed.js - Keep div scrolled to bottom as div increases in size unless user interacts with scrollbar?

I am using Typed.js to dynamically add lines of text to a div. After a few seconds, the fixed-height div develops a scrollbar. I want to keep the scrollbar scrolled to the bottom as content is continually added without editing the code of my Typed.js library.
I was able to accomplish this with the code:
window.setInterval(function() {
var elem = document.getElementById('scrollBottom');
elem.scrollTop = elem.scrollHeight;
}, 500);
However, if a user tries to scroll up, the code will obviously force the div to scroll back to the bottom. Instead, I'd like it to allow the user to scroll freely if they please (but by default, without user interaction, scroll to bottom).
The catch: I can't execute the scroll every time a new line of code is added; essentially, I do not know when it will be added. That's why I initially thought of using a frequent interval (shown above).
Is there a way to end the above function if a user clicks on the div's scrollbar?
Easy. Something like this:
var youInterval;
function startInterval(){
youInterval = setInterval(function() {
var elem = document.getElementById('scrollBottom');
elem.scrollTop = elem.scrollHeight;
}, 500);
}
//If user scrolls, stop interval
document.addEventListener('scroll', function (event) {
if (event.target.id === 'scrollBottom') { // or any other filtering condition
clearInterval(youInterval);
}
}, true /*Capture event*/);
//And start it whenever you need
startInterval();

Mousewheel and transition incompatibility

SCENARIO
I have developed a function with jQuery which listens to the user's mouse input (based on the mousewheel plugin).
I analyze the user's input with a function, and alter the default behavior of the mouse, so that it scrolls a given px value with an animation.
PROBLEM
There are div containers in the webpage that transform its size when hovered.
This causes my original mousewheel animation to delay its action for a little time (more or less, half a second). If a div is hovered, and quickly afterwards the mousewheel is rolled, the effect won't run 100% smoothly (it will cause a little lag while the scroll animation is executing, and right afterwards, it will show the animation, which was already running).
If I delete the transition in the containers, the problem is solved. However, I would like to keep the original CSS intact, and run my original animation smoothly.
How can I accomplish this?
JSFIDDLE
http://jsfiddle.net/kouk/z7p0vxpg/
JS CODE
$(function () {
function wheel($div, deltaY) {
if (deltaY == -1) {
var dest = ($(document).scrollTop()+500);
$('html,body').animate({scrollTop: dest}, 1000);
return false;
} else if (deltaY == 1) {
var dest = ($(document).scrollTop()-500);
$('html,body').animate({scrollTop: dest}, 1000);
return false;
}
}
$('html').bind('mousewheel', function (event, delta, deltaX, deltaY) {
if ($('html,body').is(":animated")){
return false;
}
if ( (delta > -2) && (delta < 2) ) {
wheel($(this), deltaY);
event.preventDefault();
console.log(delta);
}
});
});
This is a common problem with the animate() function. The previous animations are in the queue and lagging behind. You should empty the animation queue before starting the next animation to avoid the "lag" feeling.
There are two functions which let you do that : finish() and stop() . I recommend using finish as it will stop the running animation )and remove all queued animations. This was you can immediately start your latest animation.
A user doesn't necessarily want to wait for his previous animation which he has already started a new action.
Here's some sample code:
$('html,body').dequeue().animate({scrollTop: dest}, 1000);
See if the behavior is as your expected now.
And your code (updated with finish()) - http://jsfiddle.net/z7p0vxpg/15/

Hiding a DIV based on screen height with debouncing

I'm working on a page with a simple side nav that has a fixed position, set top: 30%. What I need to happen is at the bottom of the page I want to fade the menu out so it doesn't overlap the footer, the code below works but I think it is checking on scroll so much it takes to long to calculate when you scroll down fast.
Is there a faster/more lightweight way to calculate when to hide the side-nav? I'm not familiar with debouncing but would it help?
Elements:
.body-container-wrapper - total height of page
.footer-container-wrapper - total height of the footer that we want the nav to be hidden at
.internal-side-nav - the menu position: fixed, top: 30%, right: 0
Example Page: http://hsb1.hubspot.com/profile-page-template
Script:
<script type="text/javascript">
$(document).scroll(function () {
var y = $(this).scrollTop();
if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container- wrapper').outerHeight() - 400 ) {
$('.internal-side-nav').fadeIn();
} else {
$('.internal-side-nav').fadeOut();
}
});
</script>
I hadn't heard of debounce so I had to look it up. It could potentially help, but that would be an extra plugin you'd have to include and maintain and it might not work exactly how you want (I didn't see anything indicating that it does "bunches or time frames", just seemed to be bunches, which means it might fire late on you).
Instead, what you could do is throttle it yourself with a little bit of timing.
var scrollTimeInterval = 200; // how often we allow the action to trigger, in ms.
var lastScrollTime = 0;
var scrollTimeoutId = null;
$(document).scroll(function () {
var now = (new Date()).getTime();
var dScrollTime = now - lastScrollTime;
lastScrollTime = now;
if (dScrollTime < scrollTimeInterval) {
// Set a timeout so we catch the last one.
scrollTimeoutId = setTimeout(function() { $(document).scroll(); }, scrollTimeInterval - dScrollTime);
return; // too soon, so we'll skip
}
// Clear any potentially pending timeout.
clearTimeout(scrollTimeoutId);
var y = $(this).scrollTop();
if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container- wrapper').outerHeight() - 400 ) {
$('.internal-side-nav').fadeIn();
} else {
$('.internal-side-nav').fadeOut();
}
});
With this, the scroll event simply won't do anything if it hasn't been a certain amount of time since it last triggered. To ensure we catch the last scroll event (the last one before they stopped scrolling), I added a timeout which will trigger the scroll event one last time. We also have to make sure to clear that if we handle another scroll event before it fires (so we don't double up on it).
You can control the time interval it allows with the first variable. If 200ms feels a bit sluggish, you can reduce it to 100ms or 50ms and see if that gives a better balance.
Hope that helps.
Dealing with scroll events, in certain circumstances, there's not much you can do.
Solution 1: setTimeout that cancels itself on each iteration
This method is most efficient I believe, but maybe not ideal for you, because there will still be 300ms in which the sidenav would visually overlap the footer before it fades out.
var scrollTimeout = false;
$(document).scroll(function(){
clearTimeout(scrollTimeout);
var _this = this; // for the setTimeout function to know what "this" is
scrollTimeout = setTimeout(function(){
// your original code block goes here
// BUT, replace "this" with "_this"
}, 300);
});
The above essentially only runs your code when the user has not scrolled for at least 300 milliseconds.
Solution 2: Just good old optimization (not too many tricks)
This solution should hide the sidenav immediately, but still runs on every scroll, just does less
var myScrollEvent = (function(){
var $win = $(window);
var $foot = $('.footer-container-wrapper');
var footer_top = $foot.offset().top; // where on the page the footer begins
var $nav = $('.internal-side-nav');
var nav_height = $nav.height(); // maybe use outerHeight(true)?...who knows, only you
var is_hidden = false;
// this is the actual function we want to run on-scroll
return function(){
// jquery, even on fixed elements, still seems to account for scroll position
// when calculating top offset value, below is the coordinate of the bottom of the nav
var nav_bottom = $nav.offset().top + nav_height;
// if the bottom coord of the nav is lower than the top coord of the footer
if(nav_bottom > footer_top && !is_hidden){
is_hidden = true;
// hide it code
}else if(is_hidden){
is_hidden = false;
// show it code
}
};
})();
$(window).scroll(myScrollEvent);
The idea here is to cache some variables and also do the calculation a slightly different way. Your way doesn't seem by any means wrong, but this is just an alternative. Note that with this method, we're assuming the nav height will never change.
You could always combine the two solutions if you'd like as well.
Also, note that I haven't done any browser-2-browser testing, so if there are any flaws, of course let me know.

Scroll fires after jquery scrollTop animate completed

Why does another scroll event get called after a scrollTop animation fires its complete callback?
Click Handler:
var lock = false;
$('#id').click(function(event) {
var pos;
if (lock) {
return;
}
lock = true;
pos = 150;
console.log("jump start");
$(jQuery.browser.webkit ? "body": "html").animate({ scrollTop: pos }, 150, function () {
lock = false;
console.log("jump end");
});
});
Scroll Handler:
$(window).scroll(function (e) {
console.log("scrolling");
if (!lock){
alert('1');
}
});
Log:
jump start
scrolling
jump end
scrolling
demo on jsfiddle
Background
jQuery scrollTop() uses scrollTo() which is a fire and forget event. There is no stopped event for scrolling. The scroll events occur out of band from scrollTo. scrollTo means 'start scroll', a scroll event means 'scrolled (some position)'. scrollTo just initiates the starting of the scroll, it doesn't guarantee that scrolling finished when it returns. So, jQuery animation completes before final scroll (there could even be multiple scrolls backed up). The alternative would be for jQuery to wait for the position of the scroll to be what it requested (per my soln), but it does not do this
It would be nice if there was a specification that we could point to describing this, but it is just one of the Level 0 dom elements without a spec, see here. I think it makes sense the way that it works, which is why all browsers seem to implement it this way.
Why is this happening
The following occurs on the last scroll of the animation:
jquery: 'Window please scroll this last bit'
Window: 'I got this message from jquery to scroll I will start that now'
jquery: 'woohoo I am finished the animation, I will complete'
Your code: lock = false;console.log("jump end");
Window: 'I have scrolled' call scroll event handlers.'
Your code: $(window).scroll(function (e) { 'Why is this happening?'
As you can see jquery does not wait for the final scroll step of the animation to complete before completing the animation (going on to step 4). Partly this is because there is no stopped event for scrolling and partly this is because jquery does not wait for the scroll position to reach the position that was requested. We can detect when we have reached the destination position as described below.
Solutions
There is no stopped event for when scrolling completes. See here. It makes sense that there is no stopped event because the user could start scrolling again at any point, so there is no point where scrolling has really stopped - the user might just have paused for a fraction of a second.
User scrolling: For user scrolling, the normal approach is to wait some amount of time to see if scrolling is complete as described in the answer of the referenced question (bearing in mind that the user could start scrolling again).
scrollTop: However, since we know the position that we are scrolling to we can do better.
See this fiddle.
The crux of it is that since we know where we are scrolling to, we can store that position. When we reach that position we know that we are done.
The output is now:
jump start
scroll animation
jump end
The code is (note that this is based off your fiddle rather than the code in the edited question):
var scrollingTo = 0;
$('#id').click(function(event) {
if (scrollingTo) {
return;
}
console.log("jump start");
scrollingTo = 150;
$(jQuery.browser.webkit ? "body": "html").animate({ scrollTop: scrollingTo }, 150, function () {
});
});
function handleScroll()
{
if( scrollingTo !== 0 && $(window).scrollTop() == scrollingTo)
{
scrollingTo = 0;
console.log("jump end");
}
}
$(window).scroll(function (e) {
if (!scrollingTo){
console.log('user scroll');
} else {
console.log("scroll animation");
}
handleScroll();
});
I believe that at the time the animation ends and the callback function is called, the event has not reached the window yet, so it is not re-called, it just hasn't been fired yet.

On scroll load data on demand

I am implementing on demand loading of data through scrolling.
I have register the function in document.ready for a div.
The data should only be populated once the scroll is reached to the last. so to identify whether the scroll has reached till the last i am using the below function.
$("#tree").scroll(function () {
if (isScrolledToBottom(this)) {
}
});
But the function is not returning the correct value. What I mean is it should return the a true value when scroll has reached to its last.
function isScrolledToBottom(object) {
return ($(object).attr('scrollHeight') - $(object).scrollTop() - 1) <= $(object).height();
};
Try this instead :
return ($(object).attr('offsetHeight') + $(object).attr('scrollTop') >= $(object).attr('scrollHeight'));
If you want to do infinite scrolling, check out my answer here:
How to load the web page content based on user scrolling
This demo is triggered at a certain Y scroll position. If you are looking to accomplish this specifically when a user reaches a certain item, you might want to look at the Waypoints plugin.
http://imakewebthings.com/jquery-waypoints/
Infinite scroll Demo: http://imakewebthings.com/jquery-waypoints/infinite-scroll/
$('.theItems:last').waypoint(function(event, direction) {
//Ajax fetch here
});
var scrollHeight = $(object).prop("scrollHeight"); // total scrollable height of the element
var scrollTop = $(object).scrollTop(); // how far you have come from the top while scrolling vertically
var height = $(object).height(); // the constant height of the portion in display at all times
if (scrollHeight === (scrollTop + height)) {
//console.log("fetching page");
fetchNextPage();
}

Categories