Home

Awesome

ember-in-viewport

Detect if an Ember View or Component is in the viewport @ 60FPS

ember-in-viewport is built and maintained by DockYard, contact us for expert Ember.js consulting.

Read the blogpost

Download count all time npm version GitHub Actions Build Status Ember Observer Score

This Ember addon adds a simple, highly performant Service or modifier to your app. This library will allow you to check if a Component or DOM element has entered the browser's viewport. By default, this uses the IntersectionObserver API if it detects it the DOM element is in your user's browser – failing which, it falls back to using requestAnimationFrame, then if not available, the Ember run loop and event listeners.

We utilize pooling techniques to reuse Intersection Observers and rAF observers in order to make your app as performant as possible and do as little works as possible.

Demo or examples

Table of Contents

Installation

ember install ember-in-viewport

Usage

Usage is simple. First, inject the service to your component and start "watching" DOM elements.

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

export default class MyClass extends Component {
  @service inViewport

  @action
  setupInViewport() {
    const loader = document.getElementById('loader');
    const viewportTolerance = { bottom: 200 };
    const { onEnter, _onExit } = this.inViewport.watchElement(loader, { viewportTolerance });
    // pass the bound method to `onEnter` or `onExit`
    onEnter(this.didEnterViewport.bind(this));
  }

  didEnterViewport() {
    // do some other stuff
    this.infinityLoad();
  }

  willDestroy() {
    // need to manage cache yourself
    const loader = document.getElementById('loader');
    this.inViewport.stopWatching(loader);

    super.willDestroy(...arguments);
  }
}
<ul>
  <li></li>
  ...
</ul>
<div id="loader"></div>

You can also use Modifiers as well. Using modifiers cleans up the boilerplate needed and is shown in a later example.

Configuration

To use with the service based approach, simply pass in the options to watchElement as the second argument.

import Component from '@glimmer/component';
import { inject as service }  from '@ember/service';

export default class MyClass extends Component {
  @service inViewport

  @action
  setupInViewport() {
    const loader = document.getElementById('loader');

    const { onEnter, _onExit } = this.inViewport.watchElement(
      loader,
      {
        viewportTolerance: { bottom: 200 },
        intersectionThreshold: 0.25,
        scrollableArea: '#scrollable-area'
      }
    );
  }
}

Global options

You can set application wide defaults for ember-in-viewport in your app (they are still manually overridable inside of a Component). To set new defaults, just add a config object to config/environment.js, like so:

module.exports = function(environment) {
  var ENV = {
    // ...
    viewportConfig: {
      viewportUseRAF                  : true,
      viewportSpy                     : false,
      viewportListeners               : [],
      intersectionThreshold           : 0,
      scrollableArea                  : null,
      viewportTolerance: {
        top    : 0,
        left   : 0,
        bottom : 0,
        right  : 0
      }
    }
  };
};

// Note if you want to disable right and left in-viewport triggers, set these values to `Infinity`.

Modifiers

Using with Modifiers is easy.

You can either use our built in modifier {{in-viewport}} or a more verbose, but potentially more flexible generic modifier. Let's start with the former.

  1. Use {{in-viewport}} modifier on target element
  2. Ensure you have a callbacks in context for enter and/or exit
  3. options are optional - see Advanced usage (options)
<ul class="list">
  <li></li>
  <li></li>
  <div {{in-viewport onEnter=(fn this.onEnter artwork) onExit=this.onExit scrollableArea=".list"}}>
    List sentinel
  </div>
</ul>

This modifier is useful for a variety of scenarios where you need to watch a sentinel. With template only components, functionality like this is even more important! If you have logic that currently uses the did-insert modifier to start watching an element, try this one out!

If you need more than our built in modifier...

  1. Install @ember/render-modifiers
  2. Use the did-insert hook inside a component
  3. Wire up the component like so

Note - This is in lieu of a did-enter-viewport modifier, which we plan on adding in the future. Compared to the solution below, did-enter-viewport won't need a container (this) passed to it. But for now, to start using modifiers, this is the easy path.

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

export default class MyClass extends Component {
  @service inViewport

  @action
  setupInViewport() {
    const loader = document.getElementById('loader');
    const viewportTolerance = { bottom: 200 };
    const { onEnter, _onExit } = this.inViewport.watchElement(loader, { viewportTolerance });
    onEnter(this.didEnterViewport.bind(this));
  }

  didEnterViewport() {
    // do some other stuff
    this.infinityLoad();
  }

  willDestroy() {
    // need to manage cache yourself
    const loader = document.getElementById('loader');
    this.inViewport.stopWatching(loader);

    super.willDestroy(...arguments);
  }
}
<div {{did-insert this.setupInViewport}}>
  {{yield}}
</div>

Options as the second argument to inViewport.watchElement include:

IntersectionObserver's Browser Support

Out of the box

<table> <tr> <td>Chrome</td> <td>51 <sup>[1]</sup></td> </tr> <tr> <td>Firefox (Gecko)</td> <td>55 <sup>[2]</sup></td> </tr> <tr> <td>MS Edge</td> <td>15</td> </tr> <tr> <td>Internet Explorer</td> <td>Not supported</td> </tr> <tr> <td>Opera <sup>[1]</sup></td> <td>38</td> </tr> <tr> <td>Safari</td> <td>Safari Technology Preview</td> </tr> <tr> <td>Chrome for Android</td> <td>59</td> </tr> <tr> <td>Android Browser</td> <td>56</td> </tr> <tr> <td>Opera Mobile</td> <td>37</td> </tr> </table>

Running

Running Tests

Building

For more information on using ember-cli, visit http://www.ember-cli.com/.

Legal

DockYard, Inc © 2015

@dockyard

Licensed under the MIT license

Contributors

We're grateful to these wonderful contributors who've contributed to ember-in-viewport:

<a href="https://github.com/poteto"><img src="https://avatars0.githubusercontent.com/u/1390709?v=4" title="poteto" width="80" height="80"></a> <a href="https://github.com/snewcomer"><img src="https://avatars0.githubusercontent.com/u/7374640?v=4" title="snewcomer" width="80" height="80"></a> <a href="https://github.com/kellyselden"><img src="https://avatars1.githubusercontent.com/u/602423?v=4" title="kellyselden" width="80" height="80"></a> <a href="https://github.com/martndemus"><img src="https://avatars2.githubusercontent.com/u/903637?v=4" title="martndemus" width="80" height="80"></a> <a href="https://github.com/cibernox"><img src="https://avatars2.githubusercontent.com/u/265339?v=4" title="cibernox" width="80" height="80"></a> <a href="https://github.com/Alinaki"><img src="https://avatars0.githubusercontent.com/u/832780?v=4" title="Alinaki" width="80" height="80"></a> <a href="https://github.com/bcardarella"><img src="https://avatars0.githubusercontent.com/u/18524?v=4" title="bcardarella" width="80" height="80"></a> <a href="https://github.com/danorton-cubic-austin"><img src="https://avatars2.githubusercontent.com/u/11525236?v=4" title="danorton-cubic-austin" width="80" height="80"></a> <a href="https://github.com/michaeldupuisjr"><img src="https://avatars3.githubusercontent.com/u/1060866?v=4" title="michaeldupuisjr" width="80" height="80"></a> <a href="https://github.com/miguelcobain"><img src="https://avatars1.githubusercontent.com/u/631691?v=4" title="miguelcobain" width="80" height="80"></a> <a href="https://github.com/offirgolan"><img src="https://avatars2.githubusercontent.com/u/575938?v=4" title="offirgolan" width="80" height="80"></a> <a href="https://github.com/twokul"><img src="https://avatars2.githubusercontent.com/u/1131196?v=4" title="twokul" width="80" height="80"></a> <a href="https://github.com/langalex"><img src="https://avatars0.githubusercontent.com/u/2173?v=4" title="langalex" width="80" height="80"></a> <a href="https://github.com/alvincrespo"><img src="https://avatars0.githubusercontent.com/u/151311?v=4" title="alvincrespo" width="80" height="80"></a> <a href="https://github.com/blimmer"><img src="https://avatars1.githubusercontent.com/u/630449?v=4" title="blimmer" width="80" height="80"></a> <a href="https://github.com/duggiefresh"><img src="https://avatars2.githubusercontent.com/u/1206678?v=4" title="duggiefresh" width="80" height="80"></a> <a href="https://github.com/elidupuis"><img src="https://avatars3.githubusercontent.com/u/196410?v=4" title="elidupuis" width="80" height="80"></a> <a href="https://github.com/Kjaer"><img src="https://avatars0.githubusercontent.com/u/1381484?v=4" title="Kjaer" width="80" height="80"></a> <a href="https://github.com/jmurphyau"><img src="https://avatars0.githubusercontent.com/u/445432?v=4" title="jmurphyau" width="80" height="80"></a> <a href="https://github.com/jeffplang"><img src="https://avatars2.githubusercontent.com/u/102718?v=4" title="jeffplang" width="80" height="80"></a> <a href="https://github.com/jheth"><img src="https://avatars0.githubusercontent.com/u/222011?v=4" title="jheth" width="80" height="80"></a> <a href="https://github.com/jpadilla"><img src="https://avatars0.githubusercontent.com/u/83319?v=4" title="jpadilla" width="80" height="80"></a> <a href="https://github.com/les2"><img src="https://avatars3.githubusercontent.com/u/1066080?v=4" title="les2" width="80" height="80"></a> <a href="https://github.com/LevelbossMike"><img src="https://avatars3.githubusercontent.com/u/242299?v=4" title="LevelbossMike" width="80" height="80"></a> <a href="https://github.com/rwjblue"><img src="https://avatars0.githubusercontent.com/u/12637?v=4" title="rwjblue" width="80" height="80"></a> <a href="https://github.com/vasind"><img src="https://avatars0.githubusercontent.com/u/5652920?v=4" title="vasind" width="80" height="80"></a> <a href="https://github.com/hybridmuse"><img src="https://avatars0.githubusercontent.com/u/27986936?v=4" title="hybridmuse" width="80" height="80"></a> <a href="https://github.com/csand"><img src="https://avatars2.githubusercontent.com/u/142865?v=4" title="csand" width="80" height="80"></a>