Detect if user scroll away from above

The obvious answer to this is simply attaching the event to the scroll event:

var scrolled = false; $(window).scroll(function() { if($(window).scrollTop() > 0) { scrolled = true; } else { scrolled = false; } }); 

However, the creator of jQuery, John Resig blogpost since 2011 claims: Its a very, very, bad idea to attach handlers to a window scroll event.

And recommends the following:

 var didScroll = false; $(window).scroll(function() { didScroll = true; }); setInterval(function() { if ( didScroll ) { didScroll = false; // Check your page position } }, 250); 

What has changed over the past five years? Is John Resig's solution still the best?

+6
source share
4 answers

I would not want to argue with the creator of jQuery, but having a brilliant mind does not necessarily mean that you are always right, of course. Using scroll delays can ruin any event-based animation. I spent a ridiculous time studying the scroll event and creating plugins and demos based on it, and this seems like an urban myth that it causes most devices most often.

Here's a small demo to check the amount - the maximum, apparently, is the screen refresh rate:

Codepen

If the browser does not have smooth scrolling enabled, mousewheeling or panning will only trigger one event. And even if this is not the case, most machines are very well equipped to handle as many calls as the frame rate allows - provided that the rest of the web page is well built.

The last bit is the biggest problem, browsers can handle quite a bit of script at short intervals, but what can slow down execution is the heavily written markup. Therefore, the biggest bottlenecks are often repainted. This can be well verified using developer tools.

Some examples of this have large blocks of fixed position elements and animations that are not based on transform , but use pixel values ​​instead. Both run unnecessary replication, which have a huge impact on performance. These problems are not directly related to how often the lights scroll.

The use of fixed position elements can be prevented by using this hack in webkit-related browsers:

 .fixed { -webkit-backface-visibility: hidden; } 

This creates a separate stacking order, making the content independent of its environment and preventing repainting of the full page. Firefox seems to do this by default, but unfortunately I have not yet found a property that will work with IE, so it’s best to avoid such elements anyway, especially if their size is a large part of the viewport.

At the time of this writing, the following has not been implemented.

And that will be requestAnimationFrame , which now has very good browser support. At the same time, functions can be performed in such a way that they are not forced into the browser. Therefore, if there are complex functions in the scroll handler, it would be very useful to use this.

Another optimization is to use the check box and do not run anything if necessary. A useful tool in this can be adding classes and checking their presence. Here's the approach I usually use:

 $(function() { var flag, modern = window.requestAnimationFrame; $(window).scroll(function() { if (!$(this).scrollTop()) { if (flag) { if (modern) requestAnimationFrame(doSomething); else doSomething(); flag = false; } } else if (!flag) { if (modern) requestAnimationFrame(doMore); else doMore(); flag = true; } }); function doSomething() { // special magic } function doMore() { // other shenanigans } }); 

And an example of how to switch a class can be used to determine a boolean value:

 $(function() { var modern = window.requestAnimationFrame; $(window).scroll(function() { var flag = $('#element').hasClass('myclass'); if (!$(this).scrollTop()) { if (flag) { if (modern) requestAnimationFrame(doSomething); else doSomething(); $('#element').removeClass('myclass'); } } else if (!flag) { if (modern) requestAnimationFrame(doMore); else doMore(); $('#element').addClass('myclass'); } }); function doSomething() { // special magic } function doMore() { // other shenanigans } }); 

If this does not interfere with any desired functionality, debugging the event can be a good approach, of course. It could be simple:

 $(function() { var doit, modern = window.requestAnimationFrame; $(window).scroll(function() { clearTimeout(doit); doit = setTimeout(function() { if (modern) requestAnimationFrame(doSomething); else doSomething(); }, 50); }); function doSomething() { // stuff going on } }); 

In the comments below, Kayido had some valid points. Besides the need to raise the scope of the variable that determines the timeout, this approach may not be very useful in practice, because it will only perform the function after the scroll is completed. I would like to refer to this interesting article , which led to the conclusion that what would be more effective than the debut is throttling - make sure that the events control the function only with the maximum amount per unit of time, closer to what was suggested in question. Ben Alman made a nice little plugin for this (note that it was written in 2010 already).

Example

I managed to extract only the part necessary for throttling, it can still be used in the same way:

 $(window).scroll($.restrain(50, someFunction)); 

Where the first argument is the time during which only one event will execute the second parameter - the callback function. Here is the basic code:

 (function(window, undefined) { var $ = window.jQuery; $.restrain = function(delay, callback) { var executed = 0; function moduleWrap() { var elapsed = Date.now()-executed; function runIt() { executed = Date.now(); callback.apply(this, arguments); } if (elapsed > delay) runIt(); } return moduleWrap; }; })(this); 

What happens inside an executable function can still slow down the process. One example of what should always be avoided is the use of .css() to set the style and place any calculations in it. This is pretty bad for performance.

It should also be mentioned that the marking code makes more sense when switching at a certain point to the bottom of the page, and not from the top, because this position does not work repeatedly. Thus, a check of the flag itself can be excluded in the scope of the question.

Fulfilling this right, it makes sense to also check the scroll position on the page load and switch based on this. Opera is particularly stubborn with this - it resumes the cached position after the page loads. Therefore, a small timeout is best suited:

 $(window).on('load', function() { setTimeout(function() { // check scroll position and set flag }, 20); }); 

In general, I would say - do not avoid using the scroll event, it can provide very nice effects on the web page. Just be careful how it all sticks together.

+4
source

Here is the solution I am using:

 function bindScrollObserverEvent() { var self = this; $.fn.scrollStopped = function(callback) { var $this =$(this), self = this; $this.scroll(function() { if ($this.data('scrollTimeout')) { clearTimeout($this.data('scrollTimeout')); } $this.data('scrollTimeout', setTimeout(callback, 250, self)); }); }; $(window).scrollStopped(function() { //You code }); 
+1
source

In the first example, you expect the user to always start by scrolling at the top 0.

But scrolling the page and pressing F5 will lead you to the same place. So just checking > 0 will not work in this case.

 var scrolltop = $(window).scrollTop(); $(window).scroll(function() { if($(window).scrollTop() !== scrolltop) { //true } else { //false } }); 
0
source

Two fragments are different from each other: the first checks whether we are at the top of the page, and the second checks whether we scroll (whatever our position) on the page for the last 250 ms, and only then run our script.

I personally believe that even a better solution than this “recommendation”, as suggested in the comments, will cancel your event .
A small example:

 var scrolling, timeout; function callback(){ // do something } $(window).scroll(function(){ if(!scrolling){ // first time in last 250 ms we did scroll scrolling = true; callback(); timeout = setTimeout(function(){scrolling = false}, 250); } else{ // we already scrolled less than 250ms ago, reset the timeout clearTimeout(timeout); timeout = setTimeout(function(){scrolling = false}, 250); } }); 

Thus, if you stay for one hour in one position, you will not call one time your function, and in the proposed fragment you would call it 3600000 times.

What you are doing here is simply to set a 250 ms timeout and reset to each scroll event. This can happen quickly, but even if I don’t have any paper about it, I’m sure it won’t block any modern js device or interpreter.

For the first snippet, well ... really, it just serves to determine if we are at the top or not, and even if I doubt its usefulness, I don’t see how to really improve it ... except changing scrolled to notAtTop . But, as @AlexBerd pointed out, at the beginning it should not be set to false, since we don’t know the scroll position at boot, so maybe it should be var notAtTop = $(window).scrollTop() > 0;

0
source

All Articles