Merge branch 'hotfix/21.56.9' into master
[gitter.git] / public / js / utils / never-ending-story.js
blob43a858bd43d788d0f8f64f6afbbefeb432b0a2ef
1 'use strict';
3 var _ = require('lodash');
4 var Backbone = require('backbone');
5 var debug = require('debug-proxy')('app:nes');
6 var isMobile = require('./is-mobile');
7 var passiveEventListener = require('./passive-event-listener');
9 /* Put your scrolling panels on rollers */
10 function NeverEndingStory(target, options) {
11 this._target = target;
12 this._reverse = options && options.reverse;
13 this._prevScrollTop = 0;
14 this._prevScrollTime = Date.now();
15 this._nearTop = false;
16 this._nearBottom = false;
17 this._scrollHandler = _.throttle(
18 isMobile() ? this.mobileScroll.bind(this) : this.scroll.bind(this),
19 100
21 this._contentWrapper = options && options.contentWrapper;
22 this.enable();
25 _.extend(NeverEndingStory.prototype, Backbone.Events, {
26 scroll: function() {
27 var target = this._target;
29 var scrollTop = target.scrollTop;
30 var scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
32 var prevScrollTop = this._prevScrollTop;
33 var prevScrollBottom = this._prevScrollBottom;
35 this._prevScrollTop = scrollTop;
36 this._prevScrollBottom = scrollBottom;
38 var deltaTop = prevScrollTop - scrollTop;
39 var deltaBottom = prevScrollBottom - scrollBottom;
41 var halfClientHeight = target.clientHeight / 2;
42 var nearTop = scrollTop < halfClientHeight;
43 var nearBottom = scrollBottom < halfClientHeight;
45 if (deltaTop > 0 && nearTop) {
46 /* We're scrolling towards the top */
47 this.trigger('approaching.top');
48 } else if (deltaBottom > 0 && nearBottom) {
49 /* We're scrolling towards the bottom */
50 this.trigger('approaching.bottom');
53 this.scrollRate();
56 mobileScroll: function() {
57 // ios and android have to stop scrolling in order to load more content above/below the fold (see rollers.js)
58 // the best time to do that is at a natural stop, which happens to be at the start or end of the
59 // current loaded content.
61 var target = this._target;
63 var scrollTop = target.scrollTop;
64 var scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
66 var prevScrollTop = this._prevScrollTop;
67 var prevScrollBottom = this._prevScrollBottom;
69 this._prevScrollTop = scrollTop;
70 this._prevScrollBottom = scrollBottom;
72 var deltaTop = prevScrollTop - scrollTop;
73 var deltaBottom = prevScrollBottom - scrollBottom;
75 var isAtTop = scrollTop <= 0;
76 var isAtBottom = scrollBottom <= 0;
78 if (deltaTop > 0 && isAtTop) {
79 /* We're scrolling towards the top */
80 this.trigger('approaching.top');
81 } else if (deltaBottom > 0 && isAtBottom) {
82 /* We're scrolling towards the bottom */
83 this.trigger('approaching.bottom');
87 scrollRate: function() {
88 var target = this._target;
90 var now = Date.now();
91 var scrollTop = target.scrollTop;
92 var scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
94 var prevScrollTime = this._prevScrollTimeRate;
95 var prevScrollTop = this._prevScrollTopRate;
96 var prevScrollBottom = this._prevScrollBottomRate;
98 this._prevScrollTopRate = scrollTop;
99 this._prevScrollBottomRate = scrollBottom;
100 this._prevScrollTimeRate = now;
102 if (!prevScrollTime) return;
104 var deltaTop = prevScrollTop - scrollTop;
105 var deltaBottom = prevScrollBottom - scrollBottom;
106 var timeDelta = now - prevScrollTime;
107 var speed, timeToLimit;
109 if (deltaTop > 0) {
110 if (scrollTop > target.clientHeight) return;
112 speed = deltaTop / timeDelta;
113 timeToLimit = scrollTop / speed;
114 if (timeToLimit < 600) {
115 this.trigger('approaching.top');
117 return;
120 if (deltaBottom > 0) {
121 if (scrollBottom > target.clientHeight) return;
123 speed = deltaBottom / timeDelta;
124 timeToLimit = scrollBottom / speed;
126 if (timeToLimit < 600) {
127 this.trigger('approaching.bottom');
132 scrollToOrigin: function() {
133 var target = this._target;
134 if (this._reverse) {
135 var scrollTop = target.scrollHeight - target.clientHeight;
136 target.scrollTop = scrollTop;
137 } else {
138 target.scrollTop = 0;
141 this._scrollHandler();
144 pageUp: function() {
145 var target = this._target;
146 var scrollTop = target.scrollTop;
147 var pageHeight = Math.floor(target.offsetHeight * 0.8);
148 target.scrollTop = scrollTop - pageHeight;
151 pageDown: function() {
152 var target = this._target;
153 var scrollTop = target.scrollTop;
154 var pageHeight = Math.floor(target.offsetHeight * 0.8);
155 target.scrollTop = scrollTop + pageHeight;
158 enable: function() {
159 var self = this;
161 if (!this._enabled) {
162 debug('enabling scroll listener');
163 passiveEventListener.addEventListener(this._target, 'scroll', this._scrollHandler);
165 this._enabled = true;
167 // If we have a content wrapper and it's smaller than the
168 // client area, we need to load more content immediately
169 if (this._contentWrapper) {
170 setTimeout(function() {
171 if (self._contentWrapper.offsetHeight < self._target.clientHeight) {
172 self.trigger('approaching.top');
174 }, 10);
179 disable: function() {
180 if (this._enabled) {
181 debug('disabling scroll listener');
182 passiveEventListener.removeEventListener(this._target, 'scroll', this._scrollHandler);
183 this._enabled = false;
188 module.exports = NeverEndingStory;