Optimize ScrollListener performance

Listen to "scroll" event and throttle callback executions instead
of actively polling for changes to the scroll position.

Fixes #1222.
This commit is contained in:
Franz Liedke 2019-09-05 02:17:09 +02:00
parent ef38660f08
commit cf16b6c20b
3 changed files with 24 additions and 22 deletions

View File

@ -1,4 +1,4 @@
const scroll = window.requestAnimationFrame || const later = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame || window.msRequestAnimationFrame ||
@ -17,7 +17,7 @@ export default class ScrollListener {
*/ */
constructor(callback) { constructor(callback) {
this.callback = callback; this.callback = callback;
this.lastTop = -1; this.ticking = false;
} }
/** /**
@ -27,27 +27,27 @@ export default class ScrollListener {
* @protected * @protected
*/ */
loop() { loop() {
if (!this.active) return; // THROTTLE: If the callback is still running (or hasn't yet run), we ignore
// further scroll events.
if (this.ticking) return;
// Schedule the callback to be executed soon (TM), and stop throttling once
// the callback is done.
later(() => {
this.update(); this.update();
this.ticking = false;
});
scroll(this.loop.bind(this)); this.ticking = true;
} }
/** /**
* Check if the scroll position has changed; if it has, run the handler. * Run the callback, whether there was a scroll event or not.
* *
* @param {Boolean} [force=false] Whether or not to force the handler to be
* run, even if the scroll position hasn't changed.
* @public * @public
*/ */
update(force) { update() {
const top = window.pageYOffset; this.callback(window.pageYOffset);
if (this.lastTop !== top || force) {
this.callback(top);
this.lastTop = top;
}
} }
/** /**
@ -57,8 +57,10 @@ export default class ScrollListener {
*/ */
start() { start() {
if (!this.active) { if (!this.active) {
this.active = true; window.addEventListener(
this.loop(); 'scroll',
this.active = this.loop.bind(this)
);
} }
} }
@ -68,6 +70,8 @@ export default class ScrollListener {
* @public * @public
*/ */
stop() { stop() {
this.active = false; window.removeEventListener('scroll', this.active);
this.active = null;
} }
} }

View File

@ -2,7 +2,6 @@ import Component from '../../common/Component';
import ScrollListener from '../../common/utils/ScrollListener'; import ScrollListener from '../../common/utils/ScrollListener';
import PostLoading from './LoadingPost'; import PostLoading from './LoadingPost';
import anchorScroll from '../../common/utils/anchorScroll'; import anchorScroll from '../../common/utils/anchorScroll';
import mixin from '../../common/utils/mixin';
import evented from '../../common/utils/evented'; import evented from '../../common/utils/evented';
import ReplyPlaceholder from './ReplyPlaceholder'; import ReplyPlaceholder from './ReplyPlaceholder';
import Button from '../../common/components/Button'; import Button from '../../common/components/Button';
@ -586,7 +585,7 @@ class PostStream extends Component {
*/ */
unpause() { unpause() {
this.paused = false; this.paused = false;
this.scrollListener.update(true); this.scrollListener.update();
this.trigger('unpaused'); this.trigger('unpaused');
} }
} }

View File

@ -2,7 +2,6 @@ import Component from '../../common/Component';
import icon from '../../common/helpers/icon'; import icon from '../../common/helpers/icon';
import ScrollListener from '../../common/utils/ScrollListener'; import ScrollListener from '../../common/utils/ScrollListener';
import SubtreeRetainer from '../../common/utils/SubtreeRetainer'; import SubtreeRetainer from '../../common/utils/SubtreeRetainer';
import computed from '../../common/utils/computed';
import formatNumber from '../../common/utils/formatNumber'; import formatNumber from '../../common/utils/formatNumber';
/** /**
@ -365,7 +364,7 @@ export default class PostStreamScrubber extends Component {
} }
onresize() { onresize() {
this.scrollListener.update(true); this.scrollListener.update();
// Adjust the height of the scrollbar so that it fills the height of // Adjust the height of the scrollbar so that it fills the height of
// the sidebar and doesn't overlap the footer. // the sidebar and doesn't overlap the footer.