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.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
@ -17,7 +17,7 @@ export default class ScrollListener {
*/
constructor(callback) {
this.callback = callback;
this.lastTop = -1;
this.ticking = false;
}
/**
@ -27,27 +27,27 @@ export default class ScrollListener {
* @protected
*/
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.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
*/
update(force) {
const top = window.pageYOffset;
if (this.lastTop !== top || force) {
this.callback(top);
this.lastTop = top;
}
update() {
this.callback(window.pageYOffset);
}
/**
@ -57,8 +57,10 @@ export default class ScrollListener {
*/
start() {
if (!this.active) {
this.active = true;
this.loop();
window.addEventListener(
'scroll',
this.active = this.loop.bind(this)
);
}
}
@ -68,6 +70,8 @@ export default class ScrollListener {
* @public
*/
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 PostLoading from './LoadingPost';
import anchorScroll from '../../common/utils/anchorScroll';
import mixin from '../../common/utils/mixin';
import evented from '../../common/utils/evented';
import ReplyPlaceholder from './ReplyPlaceholder';
import Button from '../../common/components/Button';
@ -586,7 +585,7 @@ class PostStream extends Component {
*/
unpause() {
this.paused = false;
this.scrollListener.update(true);
this.scrollListener.update();
this.trigger('unpaused');
}
}

View File

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