Skip to content

Instantly share code, notes, and snippets.

@kdzwinel
Created July 7, 2017 08:12
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kdzwinel/48766a834225a007dd4580f84c2ddc5b to your computer and use it in GitHub Desktop.
Save kdzwinel/48766a834225a007dd4580f84c2ddc5b to your computer and use it in GitHub Desktop.
Waiting for IntersectionObserver to get a better support…
function rafThrottler(callback) {
let rafId = null;
const flush = () => {
callback();
rafId = null;
};
return () => {
if (rafId) {
return;
}
rafId = requestAnimationFrame(flush);
};
}
export default rafThrottler;
import rafThrottler from './raf_throttler';
class VisibilityObserver {
constructor() {
this.onShow = [];
this.onHide = [];
this.viewportHeight = document.documentElement.clientHeight;
this.check = rafThrottler(this.check.bind(this));
this.updateCache = rafThrottler(this.updateCache.bind(this));
if (typeof Set !== 'undefined') {
this.visible = new Set();
document.addEventListener('scroll', this.check);
window.addEventListener('resize', this.updateCache);
}
}
check() {
if (!this.visible) {
return;
}
const viewportTop = window.scrollY;
const viewportBottom = viewportTop + this.viewportHeight;
this.onShow.forEach(item => {
if (viewportBottom < item.top || viewportTop > item.bottom) {
this.visible.delete(item);
} else if (!this.visible.has(item)) {
this.visible.add(item);
item.callback();
}
});
this.onHide.forEach(item => {
if (viewportBottom > item.top && viewportTop < item.bottom) {
this.visible.add(item);
} else if (this.visible.has(item)) {
this.visible.delete(item);
item.callback();
}
});
}
updateCache() {
const currentScrollY = window.scrollY;
const oldViewPortHeight = this.viewportHeight;
const newViewportHeight = document.documentElement.clientHeight;
const viewportDelta = newViewportHeight - oldViewPortHeight;
this.viewportHeight = newViewportHeight;
function update(item) {
const elPosition = item.el.getBoundingClientRect();
item.top = elPosition.top + currentScrollY - viewportDelta * item.marginTopMultiplier;
item.bottom = elPosition.bottom + currentScrollY + viewportDelta * item.marginTopMultiplier;
}
this.onShow.forEach(update);
this.onHide.forEach(update);
}
on(event, el, callback, {marginTopMultiplier = 0, marginBottomMultiplier = 0} = {}) {
const currentScrollY = window.scrollY;
const elPosition = el.getBoundingClientRect();
const data = {
el,
callback,
top: elPosition.top + currentScrollY - marginTopMultiplier * this.viewportHeight,
bottom: elPosition.bottom + currentScrollY + marginBottomMultiplier * this.viewportHeight,
marginTopMultiplier,
marginBottomMultiplier
};
if (event === 'show') {
this.onShow.push(data);
} else if (event === 'hide') {
this.onHide.push(data);
} else {
throw new Error(`Unknown event "${event}".`);
}
this.check();
}
off(event, el, callback) {
if (event === 'show') {
this.onShow = this.onShow.filter(data => !(data.el === el && data.callback === callback));
} else if (event === 'hide') {
this.onHide = this.onHide.filter(data => !(data.el === el && data.callback === callback));
} else {
throw new Error(`Unknown event "${event}".`);
}
}
}
const observer = new VisibilityObserver();
export default observer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment