-
Notifications
You must be signed in to change notification settings - Fork 91
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
Use IntersectionObserver if available #113
Conversation
d68b0a3
to
9b3fd6e
Compare
So this solution still keeps rAF and the scrollEvent code around. ember-spaniel provides an IntersectionObserver polyfill (that doesn't fallback to native if present). But I saw this PR and thought it may be good to look at this sol'n when this is merged. Thoughts? Plus this PR wouldn't require too much of a refactor for the users whereas the IntersectionObserver only would remove a lot of the config variables and be good for a major release.... |
bind(this, this._setViewportEntered, context) | ||
); | ||
if (get(this, 'viewportUseIntersectionObserver')) { | ||
const { top, left, bottom, right } = this.viewportTolerance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe can make this a separate config item specific to IntersectionObserver instead of both IntersectionObserver and rAF. Thoughts?
const models = [...Array(10).fill().map(() => `https://s3.amazonaws.com/uifaces/faces/twitter/${images[(Math.random() * images.length) | 0]}/128.jpg`)]; | ||
|
||
export default Ember.Controller.extend({ | ||
models, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This route uses optimistic rendering. Guessing we don't want to control this behavior and leave that up to the users? i.e. 2 requests will be made - first for the 1st set of records. While that is pending, it will trigger infinityLoad()
.
addon/mixins/in-viewport.js
Outdated
if (get(this, 'viewportUseIntersectionObserver')) { | ||
const { top, left, bottom, right } = this.viewportTolerance; | ||
const options = { | ||
root: null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another question - if null, the root is the layout view (document.documentElement). One case I have run up against before is the scrollable area is not the window but rather a confined area in the viewport.
Would you be in favor of a scrollableArea
config variable?
Hey! Sry to be a bug. A review would be great when (or more apt 'if' :) ) somebody has time! 👀 |
addon/mixins/in-viewport.js
Outdated
const elementId = get(this, 'elementId'); | ||
|
||
const context = get(this, 'scrollableArea') ? get(this, 'scrollableArea') : window; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could probably make a variable on this mixin instead of this ternary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const context = get(this, 'scrollableArea') || window
?
@@ -0,0 +1,4 @@ | |||
import ScrollableRoute from './infinity-scrollable'; | |||
|
|||
export default ScrollableRoute.extend({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't necessarily like this, but to avoid duplication and allow for deterministic tests, I separated out each InfinityObserver, rAF, and scrolEvent options in the lib to three separate routes with a scrollable area.
I've tested this change on my project and it scrolls a lot smoother then the rAF approach. However I was disappointed with respect to the loading time. When having a few thousands components on a page using this mixin, it takes several seconds to load them. In my specific case, all components used the same scrollableArea and the same options. As a result, I could re-use a single IntersectionObserver instance that observes all components. This greatly improved the loading time. |
3765ea4
to
463774b
Compare
Would love to see this land! 👍 |
44e4ce4
to
5742f46
Compare
I'm going to upgrade to 2.18 and use the new testing apis in a separate commit. Getting a test leaking/flakey test. |
5752a85
to
04267ec
Compare
So since I hadn't got a review yet, I updated the pkg to 2.18 and changed everything over to the new testing apis and I know it isn't a very nice thing to do from a reviewers POV, so I can easily break the last two commits into another PR if everybody would like. However, since most of main changes are new files, upgrading didn't change much:
|
@snewcomer is it possible to submit the update to 2.18 in a separate PR? I haven't reviewed this because I'm not familiar with the codebase. TBH I don't know who is the best person to review it. |
fa83816
to
45b69f3
Compare
8a2a0f4
to
30e8aa8
Compare
@BartTK great insight btw. Curious, how did you get one IntersectionObserver to rule them all? I’m thinking your scenario might be somewhat interesting....where we could also somehow use one IntersectionObserver to ‘observe’ each element instead of many IntersectionObservers to ‘observe’ one element. Not totally sure of the perf implications, but your observation makes it seem like we should look into this. (after this PR) |
You only need a single IntersectionObserver per scrollableArea/options pair. When creating the observer, add it to a static administration. Future components having the same scrollableArea and options can re-use that IntersectionObserver instead of creating a new one. Just call 'observe' again for the new component. There are a few things to keep in mind. You need to do reference counting to know when to destroy the observer. Furthermore you cannot bind '_onIntersection' to 'this' anymore; a single callback will be used by multiple components. Instead of receiving a single entry, this callback now receives a list of entries. These entries only contain the DOM element, you need to maintain another administration to map that element to its component. In my case, this resulted in a large performance boost with respect to the loading time of the page. |
@BartTK cool 😎 so you forked and modified this PR then? Once this PR gets in, and of course if you have time, it would be a great PR! |
README.md
Outdated
- `viewportUseRAF: boolean` | ||
|
||
Default: Depends on browser | ||
|
||
As it's name suggests, if this is `true`, the Mixin will use `requestAnimationFrame` instead of the Ember run loop. Unless you want to force enabling or disabling this, you won't need to override this option. | ||
As it's name suggests, if this is `true` and the IntersectionObserver API is not available in the target browser, the Mixin will use `requestAnimationFrame`. Unless you want to force enabling or disabling this, you won't need to override this option. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its
instead of it's
addon/mixins/in-viewport.js
Outdated
const elementId = get(this, 'elementId'); | ||
|
||
const context = get(this, 'scrollableArea') ? get(this, 'scrollableArea') : window; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const context = get(this, 'scrollableArea') || window
?
tests/acceptance/infinity-test.js
Outdated
|
||
andThen(() => { | ||
assert.equal(findAll('.infinity-svg-scrollEvent').length, 20); | ||
// assert.equal(find('.infinity-scrollable-scrollEvent.inactive').length, 1, 'component is inactive after fetching more data'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented out test
return [...Array(10).fill().map(() => `${images[(Math.random() * images.length) | 0]}`)]; | ||
}, | ||
setupController(controller, model) { | ||
set(controller, 'model', model); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this the default behavior of setupController
? Is there a reason to overwrite?
<div class="list" style="height: 500px; overflow: scroll;"> | ||
<ul> | ||
{{#each model as |val|}} | ||
<div class="infinity-svg"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indent? Only to be consistent with the next file where the each
block is indented
BOOM! |
IntersectionObserver API
This PR provides an easy upgrade path to use IntersectionObserver. This also doesn't have any effect on existing users and will fallback to rAF if not in target browser.
Also added two routes -
infinity
andinfinity-scrollable
to perf-test and test.close #106 #67
Ref prev PR #112
Partial Fix #37