rnrh
rnrh

Reputation: 295

Limiting Scroll and Resize events

This is a general question about a problem I run into often, where I need something to happen at a certain screen width, or scrollTop position, so I end up triggering events for the entire scroll or resize event. This seems really unnecessary and hurts performance. I am wondering what steps I can take to limit calling code written inside scroll or resize events so that I am only triggering these events when I need them.

In the example below I just want the background color to change at a certain scrollTop offset, but since its wrapped in a scroll event, it gets trigged for every pixel.

I know there are things like lodash, but wouldn't I have the same problem of a throttle running just as often on scroll? Any general approach help would be greatly appreciated.

$(window).on('scroll', function() {
  var scrollPosition = $(window).scrollTop();
  if (scrollPosition > 500) {
    $('.container').css('background-color', 'blue');
  } else {
    $('.container').css('background-color', 'red');
  }
});
.container {
  background-color: red;
  height: 2000px;
  width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">

</div>

Upvotes: 0

Views: 2347

Answers (4)

cloned
cloned

Reputation: 6742

You should really have a look at Intersection Observer (IO), this was created to solve problems like you described.

With IO you tell the browsers which elements to watch and the browser will then execute a callback function once they come into view (or leave the view) or intersect with each other.

First you have to set the options for your observer:

let options = {
  rootMargin: '0px',
  threshold: 1.0
}

let observer = new IntersectionObserver(callback, options);

Here for example I specified that everytime the observed element is fully visible in viewport I want to execute a callback function. Obviously you can set the parameters to your liking.

Second you have to specify which elements you want to observe:

let target = document.querySelectorAll('.container');
observer.observe(target);

Here I say I want to watch all elements on the page with the class container. Last I have define the callback function which will be triggered everytime one container element is visible.

let callback = (entries, observer) => { 
  entries.forEach(entry => {
    // Each entry describes an intersection change for one observed
    // target element
  });
};

With this approach you don't have to worry about performance issues of scroll events. Since you can theoretically build all of this with listening to scroll events too you can also use this official polyfill from w3c to support older browsers.

You can also stop observing an element if you don't want to observe it anymore.

Lastly have a look at this demo, it shows you can easily change the background-color of an element depending on how much of the element is visible.

Upvotes: 1

Huangism
Huangism

Reputation: 16448

As mentioned in comments, you just need to throttle the action. Here is a sample code of how scrolling throttling would work

The expensive part of the scroll/resize event is the part where you are doing something, like when you getting the scrolltop and comparing it then running something. By throttling, your executable code don't actually run until the threshold is reached which saves you big time on performance

Resizing would be the same except you do it for the resize function of course

scrollTimer = setTimeout(function() {
  // your execution code goes here
}, 50);

var scrollInit = false;
var scrollTimer = null;

if (!scrollInit) {
  var waiting = false;

  $(window).on("scroll", function(event) {
    if (waiting) {
      return false;
    }

    clearTimeout(scrollTimer);
    waiting = true;
    scrollTimer = setTimeout(function() {
      console.log("scroll event triggered");
    }, 50);
  });
}

scrollInit = true;
div {
  background: grey;
  height: 2000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div></div>

Upvotes: 0

user9783773
user9783773

Reputation:

You can use a library like Waypoint.js to accomplish a similar feature. You'll just need to add an element at the event position and it should work quite efficient. Otherwise there aren't that many other ways except the ones that Huangism already mentioned. As you also asked about resizing events it may be better to use CSS media rules because these are quite performant and easy to use:

@media only screen and (max-width: 600px) {
  .container {
    background-color: blue;
  }
}

Upvotes: 0

Kyryl Stronko
Kyryl Stronko

Reputation: 344

You definitely should use throttle or debounce for scroll or resize handlers. It can be lodash or your own implementation. Cancelled handler triggering costs almost nothing in term of performance, so don't even bother about that.

Upvotes: 0

Related Questions