Skip to content

Commit 34db46a

Browse files
authored
[BUG] update scroll position should only be called once (#242)
1 parent 0a2fe7a commit 34db46a

File tree

2 files changed

+82
-6
lines changed

2 files changed

+82
-6
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## 3.1.3
4+
* [#242](https://github.com/DockYard/ember-router-scroll/pull/242) Unique functions to scheduleOnce...follow up
5+
* [#241](https://github.com/DockYard/ember-router-scroll/pull/241) Prevent multiple calls on routeDidChange
6+
* [#240](https://github.com/DockYard/ember-router-scroll/pull/240) Missing setter for scrollWhenAfterRender
7+
8+
## 3.1.0
9+
* [#239](https://github.com/DockYard/ember-router-scroll/pull/239) Scroll when afterRender
10+
11+
## 3.0.0
12+
* [#238](https://github.com/DockYard/ember-router-scroll/pull/238) Upgrade ember-app-scheduler to v4.0.0
13+
314
## 2.0.1
415
* [#237](https://github.com/DockYard/ember-router-scroll/pull/237) Remove recursive scroll check
516

addon/index.js

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,64 @@ import { scheduleOnce } from '@ember/runloop';
66
import { setupRouter, reset, whenRouteIdle } from 'ember-app-scheduler';
77

88
let requestId;
9+
let callbackRequestId;
10+
let idleRequestId;
11+
12+
class CounterPool {
13+
constructor() {
14+
this._counter = 0;
15+
this.onFinishedPromise = null;
16+
this.onFinishedCallback = null;
17+
18+
this.flush();
19+
}
20+
21+
get counter() {
22+
return this._counter;
23+
}
24+
set counter(value) {
25+
// put a cap so flush queue doesn't take too many paint cycles
26+
this._counter = Math.min(value, 2);
27+
}
28+
29+
flush() {
30+
if (this.counter === 0 && this.onFinishedPromise && this.onFinishedPromise.then) {
31+
// when we are done, attach a then callback and update scroll position
32+
this.onFinishedPromise.then(() => {
33+
this.onFinishedCallback && this.onFinishedCallback();
34+
});
35+
}
36+
37+
idleRequestId = window.requestAnimationFrame(() => {
38+
this.decrement();
39+
this.flush();
40+
});
41+
}
42+
43+
decrement() {
44+
this.counter = this.counter - 1;
45+
}
46+
47+
destroy() {
48+
window.cancelAnimationFrame(idleRequestId);
49+
this.counter = 0;
50+
this.onFinishedPromise = null;
51+
this.onFinishedCallback = null;
52+
}
53+
}
954

1055
// to prevent scheduleOnce calling multiple times, give it the same ref to this function
1156
const CALLBACK = function(transition) {
12-
this.updateScrollPosition(transition);
57+
callbackRequestId = window.requestAnimationFrame(() => {
58+
this.updateScrollPosition(transition);
59+
});
1360
}
1461

1562
class EmberRouterScroll extends EmberRouter {
1663
@inject('router-scroll') service;
1764

65+
idlePool;
66+
1867
@computed
1968
get isFastBoot() {
2069
const fastboot = getOwner(this).lookup('service:fastboot');
@@ -42,6 +91,10 @@ class EmberRouterScroll extends EmberRouter {
4291
window.cancelAnimationFrame(requestId);
4392
}
4493

94+
if (callbackRequestId) {
95+
window.cancelAnimationFrame(callbackRequestId);
96+
}
97+
4598
super.destroy(...arguments);
4699
}
47100

@@ -52,6 +105,11 @@ class EmberRouterScroll extends EmberRouter {
52105
* @param {transition|transition[]} transition If before Ember 3.6, this will be an array of transitions, otherwise
53106
*/
54107
updateScrollPosition(transition) {
108+
if (this.idlePool) {
109+
this.idlePool.destroy();
110+
this.idlePool = null;
111+
}
112+
55113
const url = get(this, 'currentURL');
56114
const hashElement = url ? document.getElementById(url.split('#').pop()) : null;
57115

@@ -109,13 +167,20 @@ class EmberRouterScroll extends EmberRouter {
109167

110168
if (!scrollWhenIdle && !scrollWhenAfterRender) {
111169
// out of the option, this happens on the tightest schedule
112-
scheduleOnce('render', this, CALLBACK.bind(this, transition));
113-
} else if (scrollWhenAfterRender) {
170+
scheduleOnce('render', this, CALLBACK, transition);
171+
} else if (scrollWhenAfterRender && !scrollWhenIdle) {
114172
// out of the option, this happens on the tightest schedule
115-
scheduleOnce('afterRender', this, CALLBACK.bind(this, transition));
173+
scheduleOnce('afterRender', this, CALLBACK, transition);
116174
} else {
117-
// as described in ember-app-scheduler, this addon can be used to delay rendering until after the route is idle
118-
whenRouteIdle().then(CALLBACK.bind(this, transition));
175+
if (!this.idlePool) {
176+
this.idlePool = new CounterPool();
177+
}
178+
179+
// increments happen all in one batch (before processing flush queue) and happens indeterminately
180+
// e.g. 4, 6, 10 times this could be called
181+
this.idlePool.counter = this.idlePool.counter + 1;
182+
this.idlePool.onFinishedPromise = whenRouteIdle();
183+
this.idlePool.onFinishedCallback = this.updateScrollPosition.bind(this, transition);
119184
}
120185
}
121186
}

0 commit comments

Comments
 (0)