1/** 2 * Greyd public scroll event handler. 3 * 4 * This script can handle scroll events globally to save ressources. 5 * Elements can be observed entering and/or leaving the viewport 6 * or while they are in the viewport. 7 * You can easily add elements to be observed using this function: 8 * @see greyd.scrollObserver.observeElement() 9 * 10 * @since 1.6.0 11 * @namespace greyd 12 * 13 * @note This script is not loaded by default. Currently it is only used 14 * inside the animation feature and therefore stil located here. 15 * @todo Move this script to a more appropriate location as soon 16 * as it is used in other features as well. 17 * 18 * @todo IntersectionObserver polyfill: 19 * We still need to add a polyfill for backwards compatiblity with 20 * older browsers, even though support for it is above 95% as of 21 * January 2023. 22 * @link https://caniuse.com/intersectionobserver 23 */ 24var greyd = greyd || {}; 25 26greyd.scrollObserver = new function () { 27 28 /** 29 * This class defines the way observed target elements are saved. 30 * @param {documentElement} target 31 * @param {function} callback 32 * @param {string} type 33 * 34 * @see greyd.scrollObserver.observeElement() for more details. 35 */ 36 class observedElement { 37 constructor ( target, callback, type ) { 38 this.target = target; 39 this.callback = callback; 40 this.type = typeof type === 'undefined' ? 'once' : type; 41 } 42 } 43 44 /** 45 * Holds all targets to be observed. 46 * @var {observedElement[]} 47 */ 48 this.allObservedElements = []; 49 50 /** 51 * Holds all observed targets that are currently in the viewport. 52 * @var {documentElement[]} 53 */ 54 this.allViewportElements = []; 55 56 /** 57 * Holds the IntersectionObserver. 58 * @link https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API 59 * @var {object} 60 */ 61 this.intersectionObserver; 62 63 /** 64 * Holds the configuration for the IntersectionObserver. 65 */ 66 this.intersectionObserverConfig = { 67 root: null, // The element used as the viewport for checking visibility 68 rootMargin: '0px', // Margin around the root. Can have values similar to the CSS 69 threshold: 0 // what percentage of the target is visible 70 }; 71 72 /** 73 * Init the observer. 74 * This function is automatically called once the first element 75 * is registered using `greyd.scrollObserver.observeElement()` 76 */ 77 this.init = function () { 78 79 // define the IntersectionObserver 80 if ( "IntersectionObserver" in window ) { 81 this.intersectionObserver = new IntersectionObserver( 82 ( entries ) => { 83 entries.forEach( ( entry ) => { 84 if ( entry.intersectionRatio >= 0 ) { 85 this.entersViewport( entry.target ); 86 } else { 87 this.leavesViewport( entry.target ); 88 } 89 } ); 90 }, 91 this.intersectionObserverConfig 92 ); 93 } else { 94 /** @todo fallback for IntersectionObserver */ 95 } 96 97 // define scroll event listener 98 document.addEventListener( 'scroll', this.onScroll ); 99 }; 100 101 /** 102 * Start observing an element. 103 * @param {documentElement} target DOM element. 104 * @param {function} callback Callback function. Called with target as argument. 105 * @argument {documentElement} target 106 * @argument {object} event 107 * @argument {bool} inViewport 108 * @param {string} type How to observe the element: 109 * @value once Callback is called one time as soon as the target is in the viewport. 110 * After that the element is no longer observed. 111 * @value change Target stays observed and callback is fired each time the target 112 * enters or leaves the viewport. 113 * @value always Target stays observed and callback is fired on every scroll event 114 * while the element is in the viewport. 115 */ 116 this.observeElement = function ( target, callback, type ) { 117 118 // init the observer 119 if ( ! this.intersectionObserver ) { 120 this.init(); 121 } 122 123 this.intersectionObserver.observe( target ); 124 this.allObservedElements.push( 125 new observedElement( target, callback, type ) 126 ); 127 }; 128 129 /** 130 * Stop observing an element. 131 * @param {documentElement} target DOM element. 132 */ 133 this.unobserveElement = function ( target ) { 134 this.intersectionObserver.unobserve( target ); 135 136 // remove target from current observed elements 137 let index = this.allObservedElements.indexOf( target ); 138 if ( index > -1 ) { 139 this.allObservedElements.splice( index, 1 ); 140 } 141 142 // remove target from current viewport elements 143 index = this.allViewportElements.indexOf( target ); 144 if ( index > -1 ) { 145 this.allViewportElements.splice( index, 1 ); 146 } 147 }; 148 149 /** 150 * A target element enters the viewport. 151 * @param {documentElement} target DOM element. 152 */ 153 this.entersViewport = function ( target ) { 154 155 var elements = this.getObservedElements( target );
156 if ( elements ) elements.forEach( ( element ) => { 157 158 const { callback, type } = element; 159 160 if ( type === 'always' ) { 161 // add target to current viewport elements. 162 this.allViewportElements.push( target ); 163 } 164 else if ( type === 'once' ) { 165 // unobserve, because we no longer need to watch it. 166 this.unobserveElement( target ); 167 } 168 169 callback( target, null, true ); 170 171 } ); 172 173 }; 174 175 /** 176 * A target element leaves the viewport. 177 * @param {documentElement} target DOM element. 178 */ 179 this.leavesViewport = function ( target ) { 180 181 // remove target from current viewport elements 182 const index = this.allViewportElements.indexOf( target ); 183 if ( index > -1 ) { 184 this.allViewportElements.splice( index, 1 ); 185 } 186 187 var elements = this.getObservedElements( target ); 188 if ( elements ) elements.forEach( ( element ) => { 189 190 const { callback, type } = element; 191 192 if ( type === 'change' ) { 193 // trigger change 194 callback( target, null, false ); 195 } 196 197 } ); 198 199 }; 200 201 /** 202 * A target element is in the viewport. 203 * @param {documentElement} target DOM element. 204 * @param {object} e Scroll event. 205 */ 206 this.whileInViewport = function ( target, e ) { 207 208 var elements = this.getObservedElements( target ); 209 if ( elements ) elements.forEach( ( element ) => { 210 211 const { callback } = element; 212 213 callback( target, e, true ); 214 215 } ); 216 217 }; 218 219 /** 220 * User scrolls. 221 * @param {object} e Scroll event. 222 */ 223 this.onScroll = function ( e ) { 224 225 if ( this.allViewportElements.length === 0 ) return; 226 227 this.allViewportElements.forEach( 228 target => this.whileInViewport( target, e ) 229 ) 230 }; 231 232 /** 233 * Get the observedElement objects of the current target. 234 * @since 1.13.0 235 * return array instead of single element to allow 236 * multiple callbacks on one observed element. 237 * 238 * @param {documentElement} target DOM element. 239 * @returns {observedElement[]} 240 */ 241 this.getObservedElements = function ( target ) { 242 const result = this.allObservedElements.filter( obj => { 243 return obj.target === target; 244 } ); 245 return result.length ? result : null; 246 }; 247 248 249 /** 250 * bind keyword "this" inside functions to the class object 251 */ 252 this.init = this.init.bind( this ); 253 this.observeElement = this.observeElement.bind( this ); 254 this.unobserveElement = this.unobserveElement.bind( this ); 255 this.entersViewport = this.entersViewport.bind( this ); 256 this.leavesViewport = this.leavesViewport.bind( this ); 257 this.onScroll = this.onScroll.bind( this ); 258 this.whileInViewport = this.whileInViewport.bind( this ); 259 this.getObservedElements = this.getObservedElements.bind( this ); 260};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.