0

What I'm trying to achieve:

  • If user has scrolled more than 24px from the top (origin), do something once.
  • If the user scrolls back within 24px of the top (origin), reverse that something once.

I have this code:

$("#box").scroll(function(){
    var ofs = $(".title").offset().top;
    if (ofs <= 24)
        // Do something
    else
        // Reverse that something
})

As I understand it, this function runs every time the user scrolls, which can result in hundreds of calls to the DOM.

  1. This isn't every resource efficient - Is there a more passive approach to this?
  2. Both conditions are triggered repeatedly even for small scroll amounts - any way to execute the code just once and not repeat if the same condition is true?
Abhishek
  • 4,190
  • 11
  • 39
  • 52
  • So add a check to see if the code has run, if yes, that run it, if no, than do not run it. – epascarello Sep 20 '16 at 18:26
  • 1
    debounce, then don't do anything if the condition that's being ran is the last one that was ran. – Kevin B Sep 20 '16 at 18:28
  • here's a good answer that you can use as a reference to `debounce` your scroll function: http://stackoverflow.com/a/24004942/771466 – Hayko Koryun Sep 20 '16 at 19:13
  • You can get the scroll position with `$('selector').scrollTop()`, using `.offset()` is going to cause a lot of slowdown because it has to redraw/recalculate the page to calculate the offset. – Cory Danielson Sep 20 '16 at 21:24
  • http://stackoverflow.com/questions/10966710/choppy-laggy-scroll-event-on-chrome-and-ie – Cory Danielson Sep 20 '16 at 21:26

2 Answers2

2

What you are looking to do is either throttling the requests or something called "debounce". Throttling only allows a certain number of calls to whatever in a period of time, debounce only calls the function once a certain time after action has stopped.

This is a good link explaining it: https://css-tricks.com/the-difference-between-throttling-and-debouncing/

There are several libraries out there that will do this for you like Underscore and Lodash. You can roll your own as well and the premise is basically the following for debounce:

var timer;
$('#box').scroll(function(){
    //cancel and overwrite timer if it exists already
    // set timer to execute doWork after x ms
})
function doWork(){
    //do stuff
}

You can also look into using requestAnimationFrame depending on browser support. requestAnimationFrame example and it looks like it's supported in most modern browsers and IE >= 10

Cory Danielson
  • 14,314
  • 3
  • 44
  • 51
Matti Price
  • 3,351
  • 15
  • 28
  • Thanks for the prompt response. Both of these methods - of which throttling is what I've resorted to - don't elegantly address the issue here. Find myself wishing I could use [SSE](http://www.w3schools.com/html/html5_serversentevents.asp)-like technology to get a response from the DOM instead of polling/checking it... I'l wait a day or two, if nothing better comes up, this answer wins I suppose – Abhishek Sep 20 '16 at 20:21
  • I think you are looking for what I mentioned in the last line of my answer (`requestAnimationFrame`). I am editing in a link that's more indepth about it Basically, you will have to be checking the position on each scroll but if you are only changing things if you were within X of the top etc, that's a pretty quick check that won't really impact performance. Yes it's redundant when scrolling at the bottom of the page, but there is no other way for your code to know when to trigger. – Matti Price Sep 20 '16 at 21:01
0

In the code below, everytime the user scrolls above or below that 25px threshold, one of the conditions in the if ($boxAboveBelow) if-statement will be called.

var $box = $('#box');
var $boxAboveBelow = true; // true above, false below
$box.on('scroll', function() { // Throttle this function if needed
    var newAboveBelow = $box.scrollTop() < 25;
    if (newAboveBelow !== $boxAboveBelow) {
        $boxAboveBelow = newAboveBelow;
        if ($boxAboveBelow) {
            // If the user scrolls back within 24px of the top (origin), reverse that something once.
        } else {
            // If user has scrolled more than 24px from the top (origin), do something once.
        }
    }
})

If you need those to only be called once ever, you can set Boolean variables to record if those conditions have ever been called.

var aboveCalled = false;
var belowCalled = false;
var $box = $('#box');
var $boxAboveBelow = true; // true above, false below
$box.on('scroll', function() { // Throttle this function if needed
    var newAboveBelow = $box.scrollTop() < 25;
    if (newAboveBelow !== $boxAboveBelow) {
        $boxAboveBelow = newAboveBelow;
        if ($boxAboveBelow) {
            !aboveCalled && doScrollAboveStuff();
            aboveCalled = true;
        } else {
            !belowCalled && doScrollBelowStuff();
            belowCalled = true;
        }
    if (aboveCalled && belowCalled) {
        $box.off('scroll'); // No need to keep listening, since both called
    }
});
Cory Danielson
  • 14,314
  • 3
  • 44
  • 51