import '../../polyfills/IntersectionObserver';
import customEvent from '../customEvent';
/**
* InView Class for observing elements's viewability
*
* @class InView
*/
class InView {
selector = '';
observer = null;
settings = null;
name = '';
$targets = [];
static watchers = [];
/**
* Constructor
* Register itself in a global static variable of In View Watchers
*/
constructor( settings, name ) {
this.name = name || `inView${( new Date() ).getTime()}`;
this.settings = settings;
InView.watchers[name] = this;
}
/**
* Setup intersection observer and watch for elements getting in view.
*
* @method init
*/
init() {
const options = {
root: this.settings.root ? this.settings.root : null,
rootMargin: this.settings.rootMargin ? this.settings.rootMargin : '0px',
threshold: this.settings.threshold ? this.settings.threshold : 1,
};
this.selector = this.settings.selector ? this.settings.selector : this.selector;
this.$targets = this.selector ? document.querySelectorAll( this.selector ) : false;
// Set up Intersection Observer
this.observer = new IntersectionObserver( ( entries ) => {
this.intersectionCallback( entries );
}, options );
// Start obbserving elements when a selector has been provided
if ( this.$targets ) {
[].forEach.call( this.$targets, ( $el ) => {
this.startWatching( $el );
});
}
}
/**
* Stop watching viewability of an element
*
* @method stopWatching
* @param {HTMLElement} $elem - unobserve element
*/
stopWatching( $elem ) {
this.observer.unobserve( $elem );
}
/**
* Start watching viewability of an element
*
* @method startWatching
* @param {HTMLElement} $elem - observe element
*/
startWatching( $elem ) {
// If element is already in view, trigger inView event right away,
// unless the alwaysObserve data attribute is set to true,
// also set the data attribute inView to true in case the event fires
// too early and cannot be caught in time by other code, dataset.inView
// can always be used as a flag to check
const rect = $elem.getBoundingClientRect();
let shouldObserve = true;
let margin = 0;
if ( this.settings.rootMargin ) {
margin = parseInt( this.settings.rootMargin.replace( 'px', '' ), 10 );
}
if ( rect.top > window.scrollY
&& rect.top < window.scrollY + window.innerHeight + 2 * margin ) {
$elem.dataset.inView = true; // eslint-disable-line no-param-reassign
customEvent.fire( $elem, customEvent.IN_VIEW, {
caller: this,
target: $elem,
isInView: true,
});
shouldObserve = 'true' === $elem.dataset.alwaysObserve;
}
if ( shouldObserve ) {
this.observer.observe( $elem );
}
}
/**
* Stop observing all
*
* @method stop
*/
stop() {
this.observer.disconnect();
}
/**
* Callback for Intersection Observer
* Fire inView event when element becomes in view
*
*/
intersectionCallback( entries ) {
[].forEach.call( entries, ( entry ) => {
const { target: $target } = entry;
const { isIntersecting } = entry;
$target.dataset.inView = isIntersecting.toString(); // eslint-disable-line no-param-reassign
customEvent.fire( $target, customEvent.IN_VIEW, {
caller: this,
target: $target,
isInView: isIntersecting,
ratioInView: entry.intersectionRatio,
intersectionRect: entry.intersectionRect,
rootBounds: entry.rootBounds,
clientBounds: entry.boundingClientRect,
});
// stop observing element once it's in view unless "alwaysObserve" attribute is "true"
if ( isIntersecting && 'true' !== $target.dataset.alwaysObserve ) {
this.stopWatching( $target );
}
});
}
/**
* Static method for getting a specific in view watcher by name
*
* @method getWatcher
* @param {string} name
*/
static getWatcher( name ) {
return InView.watchers[name];
}
}
export default InView;