EDIT in 2022
It might also be interesting to look at Intersection Observer that is now available in most browsers, and there is also a polyfill.
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
let isVisible=(entry.intersectionRatio===1);
});
}, {threshold:1});
observer.observe(document.querySelector('#element');
END EDIT
I have this specific need that is to trigger an event when an element becomes visible/invisible into a scrollable area. Here is the code I came to:
/**
* Create the handler
* @param {NodeElement} holder The scrollable area to monitor (where the 'scroll' event will be apply)
*/
var VisibleEventListener = function(holder) {
var _this=this;
_this.started = false;
_this.holder = holder;
// detect if an element is visible
_this.isScrolledIntoView=function(el) {
var bndElem = el.getBoundingClientRect();
var bndHolder = _this.holder.getBoundingClientRect();
return bndElem.top <= bndHolder.top ? !(bndHolder.top - bndElem.top > bndElem.height) : !(bndElem.bottom - bndHolder.bottom > bndElem.height);
}
// permits to deal with the scroll
_this.scrollHandler=function(e) {
for (var i=0, len=_this.events.length; i<len; i++) {
_this.events[i].check();
}
}
_this.events=[];
}
/**
* Add the visible/invisible event for an element into a scrollable area
* @param {NodeElement} element The element to test
* @param {String} listener 'visible' or 'invisible'
* @param {Function} callback The callback function to be called when the event is triggered
*/
VisibleEventListener.prototype.add = function(element, listener, callback) {
var _this=this;
var ElementToMonitor=function(element, listener, callback) {
this._super=_this;
this.element=element;
this.isVisible=false;
this.callback=callback;
this.listener=listener;
}
ElementToMonitor.prototype.check = function() {
var visible=this._super.isScrolledIntoView(this.element);
if (visible && !this.isVisible) { // becomes visible
this.isVisible=true;
if (this.listener==='visible') this.callback.call(this.element)
} else if (!visible && this.isVisible) { // becomes invisible
this.isVisible=false;
if (this.listener==='invisible') this.callback.call(this.element)
}
};
var etm=new ElementToMonitor(element,listener,callback);
_this.events.push(etm);
// if we have started to monitor
if (_this.started===true) etm.check();
}
VisibleEventListener.prototype.start = function() {
this.holder.addEventListener('scroll', this.scrollHandler);
this.started = true;
// trigger the check to verify if the elements are already visible
this.scrollHandler();
}
VisibleEventListener.prototype.stop = function() {
this.holder.removeEventListener('scroll', this.scrollHandler);
this.started = false;
}
And to use it:
// initiate our area with VisibleEventListener
var vel = new VisibleEventListener(document.querySelector('#s4-workspace'));
// add the elements to monitor
vel.add(document.querySelector('.slideshow'), 'invisible', function() {
console.log(this,' is now invisible')
})
vel.add(document.querySelector('.slideshow'), 'visible', function() {
console.log(this,' is now visible')
})
// start the monitoring
vel.start();