UNPKG

vpi-observer

Version:

Detect intersection between DOM elements and viewport using IntersectionObserver API

103 lines (90 loc) 3.69 kB
/* Vpi Observer @ 1.5.0 This is a default class to look up intersection between DOM Element and viewport. @params config<object>(optional) : Define IntersectionObserver API Configuration behaviour. => default : {rootMargin: '50px 0', threshold: 0.01} @params onIntersect<object|function>(required) : Callback function called on interesection. Fn onIntersect have current intersected target in first argument. => default : null @params targets<string>(required) : DOM Elements Identifier => default : [] @params verbose<booleen>(optional) : Turn on/off (true/false) verbose messaging on intersect. => default : false @params scrollPolyfill<booleen>(optional) : Turn on/off (true/false) scroll behaviour polyfill. Otherwise, trigger onIntersect<Fn> for each element, wether in viewport or not. => default : true */ export default class VpiObserver { constructor (opt = {targets}) { /* IntersectionObserver API Testing */ this.support = ('IntersectionObserver' in window) this.verbose = opt.verbose || false this.config = opt.config || {rootMargin: '50px 0', threshold: 0.01} this.targets = [...document.querySelectorAll(opt.targets)] || [] this.currentIndex = 0 this.length = this.targets.length this.onIntersectHandler = opt.onIntersect || null if (this.onIntersectHandler === null) { throw new Error('No callback provided.') } if (this.support) { this.observer = new IntersectionObserver(this.onIntersect.bind(this), this.config) this.targets.forEach(t => { this.observer.observe(t) }) } else { /* IntersectionObserver API Polyfill */ this.scrollPolyfill = opt.scrollPolyfill || true if (opt.scrollPolyfill) { this.height = window.innerHeight - parseInt(this.config.rootMargin, 10) this.targetsPos = this.targets.map(target => target.getBoundingClientRect().top) window.addEventListener('scroll', this.onScroll.bind(this), (this.supportPassive()) ? {passive: true} : false) window.addEventListener('resize', this.onResize.bind(this), (this.supportPassive()) ? {passive: true} : false) this.onScroll() } else { this.targets.forEach(target => { this.onIntersectHandler(target) }) } } } /* Test passive event configuration for performance concern and free main thread. */ supportPassive () { let supportPassive = false if (!this.support) { try { let opt = Object.defineProperty({}, 'passive', { get: function () { supportPassive = true } }) window.addEventListener('test', null, opt) } catch (e) {} } return supportPassive } /* fn onIntersect : main callback called on intersection @params entries<array> : targets observed by the IntersectionObserver API */ onIntersect (entries) { entries.forEach(entry => { if (entry.intersectionRatio > 0) { this.observer.unobserve(entry.target) this.onIntersectHandler(entry.target) } }) } onScroll (event) { let y = window.pageYOffset let currTargetsPos = this.targetsPos.map(x => x - y) currTargetsPos.forEach((pos, index) => { if (pos <= this.height) this.onIntersectHandler(this.targets[index]) }) } onResize (event) { this.height = window.innerHeight - parseInt(this.config.rootMargin, 10) } /* fn destroy : unbind observer and all event listener */ destroy () { this.targets.forEach(target => { this.observer.unobserve(target) }) window.removeEventListener('scroll', this.onScroll.bind(this), (this.supportPassive()) ? {passive: true} : false) window.removeEventListener('resize', this.onResize.bind(this), (this.supportPassive()) ? {passive: true} : false) } }