import { windowScroll } from '../DOM/WindowScroll'; import { documentSize } from '../DOM/DocumentSize'; import { windowSize } from '../DOM/WindowSize'; /** * Window events handler * * @example * ```ts * let w = new WindowEvents( window ); * * // or to change throttle delay : * * let w = new WindowEvents( window, 150 ); * * let callback = ( { windowInfo, scrollInfo, documentInfo, viewportInfo }, type, event ) => {}; * * w.register( callback, 'resize' ); // type = 'resize', 'scroll' or undefined for both * w.remove( callback, 'resize' ); * * // Force refresh of all registered function * w.refresh( 'scroll' ); * * // Force the computing scroll position window and document size * w.update(); * * // Call the function * w.get( callback ); * * // Tools * { top, left } = w.scrollInfo; * { width, height } = w.windowInfo; * { width, height } = w.documentInfo; * { top, left, bottom, right, width, height } = w.viewportInfo; * ``` */ export default class WindowEvents { #tick; #windowInfo; #scrollInfo; #documentInfo; #viewportInfo; #isActive; #$window; #throttleDelay; #resizeFunctionSet = new Set(); #scrollFunctionSet = new Set(); /** * Get the last stored scroll position */ get scrollInfo(): FLib.Events.WindowEvents.ScrollInfo { return this.#scrollInfo; } /** * Get the last stored window size */ get windowInfo(): FLib.Events.WindowEvents.WindowInfo { return this.#windowInfo; } /** * Get the last stored document size */ get documentInfo(): FLib.Events.WindowEvents.DocumentInfo { return this.#documentInfo; } /** * Get the last stored viewport information * * The viewport is the displayed part of the document.
* So, its top and left are the scroll position and its width and height are the window size.
*/ get viewportInfo(): FLib.Events.WindowEvents.ViewportInfo { return this.#viewportInfo; } /** * @param $window - DOM object on which the events will be checked * @param throttleDelay - Throttle delay in ms. If < 0, it use requestAnimationFrame */ constructor( $window: HTMLElement | Window, throttleDelay = -1 ) { this.#$window = $window; this.#throttleDelay = throttleDelay; } // Call each registered function for resize event #updateResize = ( originalEvent?: Event ): void => { if ( !this.#resizeFunctionSet.size ) { return; } this.#resizeFunctionSet.forEach( fcn => { ( fcn as FLib.Events.WindowEvents.Callback )( { "windowInfo": this.#windowInfo, "scrollInfo": this.#scrollInfo, "documentInfo": this.#documentInfo, "viewportInfo": this.#viewportInfo }, 'resize', originalEvent ); } ); } // Call each registered function for scroll event #updateScroll = ( originalEvent?: Event ): void => { if ( !this.#scrollFunctionSet.size ) { return; } this.#scrollFunctionSet.forEach( fcn => { ( fcn as FLib.Events.WindowEvents.Callback )( { "windowInfo": this.#windowInfo, "scrollInfo": this.#scrollInfo, "documentInfo": this.#documentInfo, "viewportInfo": this.#viewportInfo }, 'scroll', originalEvent ); } ); } #updateValue = ( type?: FLib.Events.WindowEvents.Type ): void => { if ( type !== 'resize' ) { this.#scrollInfo = windowScroll(); } if ( type !== 'scroll' ) { this.#documentInfo = documentSize(); this.#windowInfo = windowSize(); } this.#viewportInfo = { "top": this.#scrollInfo.top, "left": this.#scrollInfo.left, "bottom": this.#scrollInfo.top + this.#windowInfo.height, "right": this.#scrollInfo.left + this.#windowInfo.width, "width": this.#windowInfo.width, "height": this.#windowInfo.height }; } #changeHandler = ( e: Event ): void => { if ( this.#tick ) { return; } this.#tick = true; const FNC = ( ( ev: Event ) => { return () => { const TYPE = ev.type === 'scroll' ? 'scroll' : 'resize'; this.#updateValue( TYPE ); this.refresh( TYPE, ev ); this.#tick = false; } } )( e ); if ( this.#throttleDelay < 0 ) { window.requestAnimationFrame( FNC ); return; } setTimeout( FNC, this.#throttleDelay ); } /** * Register a function on a type of event * * @param type - resize | scroll | undefined (both) */ register( callback: FLib.Events.WindowEvents.Callback, type?: FLib.Events.WindowEvents.Type ): this { if ( type !== 'scroll' ) { this.#resizeFunctionSet.add( callback ); } if ( type !== 'resize' ) { this.#scrollFunctionSet.add( callback ); } if ( !this.#isActive && ( this.#resizeFunctionSet.size || this.#scrollFunctionSet.size ) ) { this.#$window.addEventListener( 'resize', this.#changeHandler ); this.#$window.addEventListener( 'scroll', this.#changeHandler ); this.#updateValue(); this.#isActive = true; } return this; } /** * Unregister a function for a type of event * * @param type - resize | scroll | undefined (both) */ remove( callback: FLib.Events.WindowEvents.Callback, type?: FLib.Events.WindowEvents.Type ): this { if ( type !== 'scroll' ) { this.#resizeFunctionSet.delete( callback ); } if ( type !== 'resize' ) { this.#scrollFunctionSet.delete( callback ); } // No function registered, no need to check if ( !this.#resizeFunctionSet.size && !this.#scrollFunctionSet.size ) { this.#$window.removeEventListener( 'resize', this.#changeHandler ); this.#$window.removeEventListener( 'scroll', this.#changeHandler ); this.#isActive = false; } return this; } /** * Update all the stored data (window size, scroll position, ...) * * @param type - resize | scroll | undefined (both) */ update( type?: FLib.Events.WindowEvents.Type ): this { this.#updateValue( type ); return this; } /** * Refresh all the registered functions * * @param type - resize | scroll | undefined (both) */ refresh( type?: FLib.Events.WindowEvents.Type , _oe?: Event ): this { if ( type !== 'scroll' ) { this.#updateResize( _oe ); } if ( type !== 'resize' ) { this.#updateScroll( _oe ); } return this; } /** * Call a function with the last stored positions and sizes */ get( callback: FLib.Events.WindowEvents.Callback ): this { callback({ "windowInfo": this.#windowInfo, "scrollInfo": this.#scrollInfo, "documentInfo": this.#documentInfo, "viewportInfo": this.#viewportInfo }, 'force' ); return this; } }