Last Updated: January 28, 2020
·
15.96K
· mgrassotti

Binding to scroll events with Ember.js

You can learn a lot by reading the discourse source code. Today I found a nice mixin which makes it easy to get a notification when the browser window is scrolled. Here's what I learned...

Binding to browser scroll/touch events is easy:

App.Scrolling = Em.Mixin.create({

  bindScrolling: function(opts) {
    var onScroll, _this = this;

    onScroll = function(){ 
        return _this.scrolled(); 
    };

    $(document).bind('touchmove', onScroll);
    $(window).bind('scroll', onScroll);
  },

  unbindScrolling: function() {
    $(window).unbind('scroll');
    $(document).unbind('touchmove');
  }

});

Using the above mixin, you can bind/unbind to scroll events from any view:

App.MyView = Ember.View.extend(App.Scrolling, {
    didInsertElement: function() {
        this.bindScrolling();
    },
    willRemoveElement: function() {
        this.unbindScrolling();
    },
    scrolled: function() {
      console.log('MyView was scrolled')
    }
});

Scroll events probably need to be debounced!

Scroll events are triggered very quickly, so the above view's scroll() method is going to get called like crazy. The only sane way to do this is by debouncing. Discourse does this by adding a debounce() function to the application namespace. Assuming we have our own App.debounce the scrollable mixin can use it to throttle scroll notifications:

App.Scrolling = Em.Mixin.create({

  bindScrolling: function(opts) {
    var onScroll, _this = this;
    opts = opts || {debounce: 100};

    if (opts.debounce) {
      onScroll = App.debounce(function() { return _this.scrolled(); }, 100);
    } else {
      onScroll = function(){ 
          return _this.scrolled(); 
      };
    }
    $(document).bind('touchmove', onScroll);
    $(window).bind('scroll', onScroll);
  },

  unbindScrolling: function() {
    $(window).unbind('scroll');
    $(document).unbind('touchmove');
  }

});

You can find the original version (in the discourse github rebo)[https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/mixins/scrolling.js]

5 Responses
Add your response

Dayum. That's really nice man. Good find!!!

over 1 year ago ·

A change in Ember has made this a bit different now. You need to bind to 'willDestroyElement' instead of 'willRemoveElement'

over 1 year ago ·

Great discovery!
Since Ember 1.0.0 it's also possible to use Ember.run.debounce http://emberjs.com/api/classes/Ember.run.html#method_debounce

over 1 year ago ·

This is great! Though, I ran into the issue when more than one view is using this mixin. I'd recommend to change it to the following:


bindScrolling() { Ember.$(document).bind('touchmove', this.scrolled); Ember.$(window).bind('scroll', this.scrolled); }, unbindScrolling(){ Ember.$(window).unbind('scroll', this.scrolled); Ember.$(document).unbind('touchmove', this.scrolled); } </code> </pre> This way you are just binding the specific view's methods, and you're not unbinding all functions when you do unbind scrolling.
over 1 year ago ·

Hey Michael, very helpful tip. I noticed that you forgot to use your debounce option in the actual debounce call. Instead of 100 it should be opts.debounce. Secondly, is there a reason you are using .bind() and .unbind() instead of the recommended .on() and .off()?

over 1 year ago ·