vpi-observer
Version:
Detect intersection between DOM elements and viewport using IntersectionObserver API
103 lines (90 loc) • 3.69 kB
JavaScript
/*
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)
}
}