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.
1 2 3 4 5 6 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | /** * 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:
1 2 3 4 5 6 7 8 9 10 11 | // 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(); |