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();