core/modules/inview.js

import * as R from 'ramda'

export function inview(base = document, events = {}) {
	if (!events) return

	const defaultOptions = {
		rootMargin: '0px',
		threshold: 0
	}

	let isActive = false
	let observer

	const onIntersection = entries => {
		entries.forEach(item => {
			const { isIntersecting } = item
			const type = isIntersecting ? 'enter' : 'exit'
			if (events[type]) {
				const remove = events[type](item)

				if (remove) {
					observer.unobserve(item.target)
				}
			}
		})
	}

	function watch({ selector, options = defaultOptions }) {
		if (window.IntersectionObserver) {
			isActive = true
			observer = new IntersectionObserver(onIntersection, options)
			const nodes =
				typeof selector === 'string'
					? [...base.querySelectorAll(selector)]
					: selector

			R.forEach(item => {
				observer.observe(item)
			})(nodes)
		}
	}

	function destroy() {
		if (observer && isActive && window.IntersectionObserver) {
			observer.disconnect()
			isActive = false
		}
	}

	return {
		destroy,
		watch
	}
}

/**
 * Create a router
 * @memberof Behaviour
 * @mixin InviewMixin
 * @description class used to manage elements entering/exiting the viewport
 * @example
 * import InviewMixin, { mix } from '@/core/ScreenMixin'
 * import {
 * 	RefsMixin,
 * } from '@/core/modules/'
 *
 * export default class ExampleWithAllTheThings extends mix(Behaviour).with(
 * 	InviewMixin
 * ) {
 * 	mount = () => {
 * 		this.$$inview.watch({
 * 			selector: '[data-element]'
 * 		})
 * 	}
 *
 * 	viewport = {
 *		enter: node => {
 *			log('exit', node)
 *		},
 *
 *		exit: node => {
 *			log('exit', node)
 *		}
 *	}
 * }
 * @return {InviewMixin}
 */
export const InviewMixin = superclass =>
	class extends superclass {
		init() {
			const events = this.viewport
			const base = this.$el
			this.$$inview = inview(base, events)

			if (super.init) super.init()

			return this
		}

		destroy() {
			this.$$inview.destroy()
			if (super.destroy) super.destroy()
		}
	}