Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursively check document height before scrollTo #217

Merged
merged 24 commits into from
Sep 11, 2019

Conversation

snewcomer
Copy link
Collaborator

@snewcomer snewcomer commented Sep 10, 2019

For heavy content pages, the window height might not be filled in all the way by the time you call scrollTo. As a result, clicking the back/forward button will just end up at the top of the page. We can start calling recursively the same scrollTo until we have a height that is >= than the previous y.

For scrolling to last remembered position, we need it to call scrollTo(x,y) at the exact right moment and not a minute early or late. This solution uses recursive rAF callbacks to check and execute.

ember-app-scheduler is a brilliant little library. However, whenRouteIdle and whenRoutePainted are too late (you will see the top of the page before scrolling to last remembered position). We ideally need to avoid these artifacts in the out of the box implementation (although you might want ember-app-scheduler's afterPaint or afterIdle scrollTo though

@snewcomer snewcomer self-assigned this Sep 10, 2019
addon/index.js Outdated

if (
(documentWidth + scrollBarWidth - window.innerWidth >= scrollHash.x
&& documentHeight + scrollBarWidth - window.innerHeight >= scrollHash.y)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of sync forced layout/reflow issues when requested :(

@snewcomer
Copy link
Collaborator Author

I don't think this is a good idea. rAF runs every 16ms so I'm wondering if we can do this in a rAF pool where we recursively check in a requestAnimationFrame if these conditions are true.

if (targetElement || 'window' === scrollElement) {
if (recursiveCheck) {
// our own implementation
tryScrollRecursively(window.scrollTo, scrollPosition);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also want to do this if the page is optimistically rendered.

@snewcomer snewcomer requested a review from jleja September 11, 2019 05:00
addon/index.js Outdated
@@ -103,12 +145,20 @@ let RouterScrollMixin = Mixin.create({
}

const delayScrollTop = get(this, 'service.delayScrollTop');
const afterPaint = get(this, 'service.afterPaint');
const afterIdle = get(this, 'service.afterIdle');
Copy link
Collaborator Author

@snewcomer snewcomer Sep 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these aren't strictly necessary for this PR. Likely users won't want either (b/c it leaves artifacts for most route transition). However, I added and documented just to test/make sure this PR does what is says it does and I can test which implementation works best for the apps I work on

Copy link
Contributor

@jleja jleja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! Mainly comments around wording/consistency.

README.md Outdated
the First Meaningful Paint using `delayScrollTop: true` in your config. `delayScrollTop` defaults to `false`.
#### Scroll Timing

You may want the default out of the box behaviour. We schedule scroll after Ember's `render`. This occurs on the tightest schedule between route transition start and end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/out of the box/"out of the box"

addon/index.js Outdated

if (documentWidth + scrollBarWidth - window.innerWidth >= scrollHash.x
&& documentHeight + scrollBarWidth - window.innerHeight >= scrollHash.y
|| ATTEMPTS >= 60) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If 60 is a magic number, maybe set it as a const

addon/index.js Outdated
if (requestId) {
window.cancelAnimationFrame(requestId);
}

this._super(...arguments);
},

/**
* Updates the scroll position
* @param {transition|transition[]} transition If before Ember 3.6, this will be an array of transitions, otherwise
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this @param be above the @method?

addon/index.js Outdated
this._super(...arguments);
},

/**
* Updates the scroll position
* @param {transition|transition[]} transition If before Ember 3.6, this will be an array of transitions, otherwise
* it will be a single transition
* @method updateScrollPosition
* @param {Object} transition
* @param {Boolean} recursiveCheck - if check until document height is same or lger than than y
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description of this param could read a little better. if "true", check until document height is >= y. Maybe replace y with what it actually is.

addon/index.js Outdated
@@ -103,12 +150,20 @@ let RouterScrollMixin = Mixin.create({
}

const delayScrollTop = get(this, 'service.delayScrollTop');
const whenPainted = get(this, 'service.whenPainted');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the expense of being wordier, I'm wondering if these new properties could be more explicit. Like, scrollWhenPainted and scrollWhenIdle or something like that? Also -- would settled be a more accurate description than idle?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great idea! changed

@@ -47,7 +50,10 @@ const RouterScroll = Service.extend({

// if we are looking to where to transition to next, we need to set the default to the position
// of the targetElement on screen
set(scrollMap, 'default', { x, y });
set(scrollMap, 'default', {
x,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent formatting between this and line 26.

@@ -63,7 +69,10 @@ const RouterScroll = Service.extend({

// only a `key` present after first load
if (key && 'number' === typeOf(x) && 'number' === typeOf(y)) {
set(scrollMap, key, { x, y });
set(scrollMap, key, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, too.

@@ -84,10 +93,16 @@ const RouterScroll = Service.extend({
set(this, 'targetElement', targetElement);
}

const delayScrollTop = config.routerScroll.delayScrollTop;
const { whenPainted, whenIdle, delayScrollTop } = config.routerScroll;
if (delayScrollTop === true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these properties default to false and only get set to true when set by the user, it might be simpler to use something like:

setProperties(this, {
  delayScrollTop,
  whenPainted,
  whenIdle
});

@snewcomer
Copy link
Collaborator Author

@jleja Really appreciate the thoroughness. All your considerations were implemented here!

Copy link
Contributor

@jleja jleja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

@snewcomer snewcomer merged commit 5b73f0a into master Sep 11, 2019
@snewcomer snewcomer deleted the sn/recursive-scroll-check branch September 11, 2019 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants