diff --git a/README.md b/README.md index 76ef95d6..5b2ecf1d 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,34 @@ export default Ember.Component.extend(InViewportMixin, { ### Basic usage #### Available hooks +##### `didEnterViewport`, `didExitViewport` +These hooks fire once whenever the `Component` enters or exits the viewport. You can handle them the same way you would handle any other native Ember hook: + +```js +export default Ember.Component.extend(InViewportMixin, { + + // with prototype extensions disabled + handleDidEnterViewport: Ember.on('didEnterViewport', function() { + console.log('entered'); + }), + + handleDidExitViewport: Ember.on('didExitViewport', function() { + console.log('exited'); + }), + + // with prototype extensions enabled + didEnterViewport() { + console.log('entered'); + }, + + didExitViewport() { + console.log('exited'); + } +}); +``` + ##### `viewportEntered` -This hook fires whenever the `Component` enters the viewport. To apply an `.active` class to your `Component` when it enters the viewport, you can simply bind the `active` class to the mixed in property `viewportEntered`, like so: +To apply an `.active` class to your `Component` when it enters the viewport, you can simply bind the `active` class to the mixed in property `viewportEntered`, like so: ```js export default Ember.Component.extend(InViewportMixin, { diff --git a/addon/mixins/in-viewport.js b/addon/mixins/in-viewport.js index 2a13efbe..a206a34c 100644 --- a/addon/mixins/in-viewport.js +++ b/addon/mixins/in-viewport.js @@ -36,7 +36,6 @@ export default Ember.Mixin.create({ _setInitialState: on('init', function() { setProperties(this, { - $viewportCachedEl : undefined, viewportUseRAF : canUseRAF(), viewportEntered : false, viewportSpy : false, @@ -53,13 +52,10 @@ export default Ember.Mixin.create({ _setupElement: on('didInsertElement', function() { if (!canUseDOM) { return; } - const viewportUseRAF = get(this, 'viewportUseRAF'); - this._setInitialViewport(window); this._addObserverIfNotSpying(); - this._setViewportEntered(window); - if (!viewportUseRAF) { + if (!get(this, 'viewportUseRAF')) { forEach(listeners, (listener) => { const { context, event } = listener; this._bindListeners(context, event); @@ -72,49 +68,52 @@ export default Ember.Mixin.create({ }), _addObserverIfNotSpying() { - const viewportSpy = get(this, 'viewportSpy'); - - if (!viewportSpy) { - this.addObserver('viewportEntered', this, this._viewportDidEnter); + if (!get(this, 'viewportSpy')) { + this.addObserver('viewportEntered', this, this._unbindIfEntered); } }, _setViewportEntered(context = null) { Ember.assert('You must pass a valid context to _setViewportEntered', context); - const $viewportCachedEl = get(this, '$viewportCachedEl'); - const viewportUseRAF = get(this, 'viewportUseRAF'); - const elementId = get(this, 'elementId'); - const tolerance = get(this, 'viewportTolerance'); - const height = $(context) ? $(context).height() : 0; - const width = $(context) ? $(context).width() : 0; - - let boundingClientRect; - - if ($viewportCachedEl) { - boundingClientRect = $viewportCachedEl[0].getBoundingClientRect(); - } else { - boundingClientRect = set(this, '$viewportCachedEl', this.$())[0].getBoundingClientRect(); - } - - const viewportEntered = isInViewport(boundingClientRect, height, width, tolerance); + const viewportUseRAF = get(this, 'viewportUseRAF'); + const viewportTolerance = get(this, 'viewportTolerance'); + const elementId = get(this, 'elementId'); + const boundingClientRect = get(this, 'element').getBoundingClientRect(); + const contextEl = $(context); + const height = contextEl.height(); + const width = contextEl.width(); - set(this, 'viewportEntered', viewportEntered); + this._triggerDidEnterViewport( + isInViewport(boundingClientRect, height, width, viewportTolerance) + ); - if ($viewportCachedEl && viewportUseRAF) { + if (boundingClientRect && viewportUseRAF) { rAFIDS[elementId] = window.requestAnimationFrame( bind(this, this._setViewportEntered, context) ); } }, - _viewportDidEnter() { - const viewportEntered = get(this, 'viewportEntered'); - const viewportSpy = get(this, 'viewportSpy'); + _triggerDidEnterViewport(hasEnteredViewport = false) { + const viewportEntered = get(this, 'viewportEntered'); + const didEnter = !viewportEntered && hasEnteredViewport; + const didLeave = viewportEntered && !hasEnteredViewport; + let triggeredEventName = ''; + + if (didEnter) { triggeredEventName = 'didEnterViewport'; } + if (didLeave) { triggeredEventName = 'didExitViewport'; } + + this.trigger(triggeredEventName); - if (!viewportSpy && viewportEntered) { + set(this, 'viewportEntered', hasEnteredViewport); + }, + + _unbindIfEntered() { + if (!get(this, 'viewportSpy') && get(this, 'viewportEntered')) { this._unbindListeners(); - this.removeObserver('viewportEntered', this, this._viewportDidEnter); + this.removeObserver('viewportEntered', this, this._unbindIfEntered); + set(this, 'viewportEntered', true); } }, @@ -129,35 +128,24 @@ export default Ember.Mixin.create({ _scrollHandler(context = null) { Ember.assert('You must pass a valid context to _scrollHandler', context); - const viewportRefreshRate = get(this, 'viewportRefreshRate'); - - debounce(this, function() { + debounce(this, () => { this._setViewportEntered(context); - }, viewportRefreshRate); + }, get(this, 'viewportRefreshRate')); }, _bindListeners(context = null, event = null) { Ember.assert('You must pass a valid context to _bindListeners', context); Ember.assert('You must pass a valid event to _bindListeners', event); - const elementId = get(this, 'elementId'); - - Ember.warn('No elementId was found on this Object, `viewportSpy` will' + - 'not work as expected', elementId); - - $(context).on(`${event}#${elementId}`, () => { + $(context).on(`${event}#${get(this, 'elementId')}`, () => { this._scrollHandler(context); }); }, _unbindListeners() { - const elementId = get(this, 'elementId'); - const viewportUseRAF = get(this, 'viewportUseRAF'); - - Ember.warn('No elementId was found on this Object, `viewportSpy` will' + - 'not work as expected', elementId); + const elementId = get(this, 'elementId'); - if (viewportUseRAF) { + if (get(this, 'viewportUseRAF')) { next(this, () => { window.cancelAnimationFrame(rAFIDS[elementId]); rAFIDS[elementId] = null; diff --git a/package.json b/package.json index 2cb3a364..63203afd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-in-viewport", - "version": "1.0.0", + "version": "1.1.0", "description": "Detect if an Ember View or Component is in the viewport @ 60FPS", "directories": { "doc": "doc",