From 3b8022c3c18ff7c3f3a5a4d1aef80dada2a3b72e Mon Sep 17 00:00:00 2001 From: alistair3149 Date: Wed, 6 Nov 2024 19:31:49 -0500 Subject: [PATCH] =?UTF-8?q?refactor(core):=20=E2=99=BB=EF=B8=8F=20set=20up?= =?UTF-8?q?=20scroll=20direction=20observer=20in=20setupObservers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is needed for centralizing the observers. --- .../skins.citizen.scripts/scrollObserver.js | 55 +++++++++++-------- .../skins.citizen.scripts/setupObservers.js | 34 +++++++++++- resources/skins.citizen.scripts/skin.js | 2 - .../skins.citizen.scripts/stickyHeader.js | 30 +--------- 4 files changed, 66 insertions(+), 55 deletions(-) diff --git a/resources/skins.citizen.scripts/scrollObserver.js b/resources/skins.citizen.scripts/scrollObserver.js index c4e6965e..63048c82 100644 --- a/resources/skins.citizen.scripts/scrollObserver.js +++ b/resources/skins.citizen.scripts/scrollObserver.js @@ -4,36 +4,47 @@ * @param {Function} onScrollDown - Function to be called when scrolling down. * @param {Function} onScrollUp - Function to be called when scrolling up. * @param {number} threshold - The threshold for significant scroll position change. + * @return {Object} */ -function initDirectionObserver( onScrollDown, onScrollUp, threshold ) { +function initDirectionObserver( onScrollDown, onScrollUp, threshold = 0 ) { let lastScrollTop = 0; let lastScrollDirection = ''; let isScrolling = false; - window.addEventListener( 'scroll', () => { + const handleScroll = () => { + const currentScrollTop = window.scrollY; + + if ( Math.abs( currentScrollTop - lastScrollTop ) < threshold ) { + isScrolling = false; + return; + } + + if ( currentScrollTop > lastScrollTop && lastScrollDirection !== 'down' ) { + lastScrollDirection = 'down'; + onScrollDown(); + } else if ( currentScrollTop < lastScrollTop && lastScrollDirection !== 'up' ) { + lastScrollDirection = 'up'; + onScrollUp(); + } + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + isScrolling = false; + }; + + const onScroll = () => { if ( !isScrolling ) { - window.requestAnimationFrame( () => { - const currentScrollTop = window.scrollY; - - if ( Math.abs( currentScrollTop - lastScrollTop ) < threshold ) { - isScrolling = false; - return; - } - - if ( currentScrollTop > lastScrollTop && lastScrollDirection !== 'down' ) { - lastScrollDirection = 'down'; - onScrollDown(); - } else if ( currentScrollTop < lastScrollTop && lastScrollDirection !== 'up' ) { - lastScrollDirection = 'up'; - onScrollUp(); - } - // For Mobile or negative scrolling - lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; - isScrolling = false; - } ); + window.requestAnimationFrame( handleScroll ); isScrolling = true; } - } ); + }; + + return { + resume: () => { + window.addEventListener( 'scroll', onScroll ); + }, + pause: () => { + window.removeEventListener( 'scroll', onScroll ); + } + }; } /** diff --git a/resources/skins.citizen.scripts/setupObservers.js b/resources/skins.citizen.scripts/setupObservers.js index da9086ff..c3774a24 100644 --- a/resources/skins.citizen.scripts/setupObservers.js +++ b/resources/skins.citizen.scripts/setupObservers.js @@ -3,6 +3,7 @@ const scrollObserver = require( './scrollObserver.js' ), resizeObserver = require( './resizeObserver.js' ), initSectionObserver = require( './sectionObserver.js' ), + stickyHeader = require( './stickyHeader.js' ), initTableOfContents = require( './tableOfContents.js' ), deferUntilFrame = require( './deferUntilFrame.js' ), TOC_ID = 'citizen-toc', @@ -16,7 +17,9 @@ const .map( ( sel ) => `.mw-parser-output ${ sel }` ).join( ', ' ), HEADLINE_SELECTOR = [ '.mw-headline', ...HEADING_TAGS.map( ( tag ) => `${ tag }[id]` ) ] .map( ( sel ) => `.mw-parser-output ${ sel }` ).join( ', ' ), - TOC_SECTION_ID_PREFIX = 'toc-'; + TOC_SECTION_ID_PREFIX = 'toc-', + SCROLL_DOWN_CLASS = 'citizen-scroll--down', + SCROLL_UP_CLASS = 'citizen-scroll--up'; /** * @callback OnIntersection @@ -194,7 +197,27 @@ const main = () => { const tableOfContents = isToCUpdatingAllowed ? setupTableOfContents( tocElement, bodyContent, initSectionObserver ) : null; - const pagetitleObserver = scrollObserver.initScrollObserver( + const stickyIntersection = document.getElementById( 'citizen-page-header-sticky-sentinel' ); + const isStickyHeaderAllowed = !!stickyIntersection; + + const scrollDirectionObserver = scrollObserver.initDirectionObserver( + () => { + document.body.classList.remove( SCROLL_UP_CLASS ); + document.body.classList.add( SCROLL_DOWN_CLASS ); + }, + () => { + document.body.classList.remove( SCROLL_DOWN_CLASS ); + document.body.classList.add( SCROLL_UP_CLASS ); + }, + 50 + ); + + if ( isStickyHeaderAllowed ) { + stickyHeader.init(); + scrollDirectionObserver.resume(); + } + + const pageHeaderObserver = scrollObserver.initScrollObserver( () => { // TODO: Below page header }, @@ -203,15 +226,20 @@ const main = () => { } ); + pageHeaderObserver.observe( stickyIntersection ); + const bodyObserver = resizeObserver.initResizeObserver( () => { - // Disable all CSS transition during resize + // Disable all CSS animation during resize if ( document.documentElement.classList.contains( 'citizen-animations-ready' ) ) { document.documentElement.classList.remove( 'citizen-animations-ready' ); } + scrollDirectionObserver.pause(); }, () => { + // Enable CSS animation after resize is finished document.documentElement.classList.add( 'citizen-animations-ready' ); + scrollDirectionObserver.resume(); } ); bodyObserver.observe( document.body ); diff --git a/resources/skins.citizen.scripts/skin.js b/resources/skins.citizen.scripts/skin.js index 28bf1e1b..6079f213 100644 --- a/resources/skins.citizen.scripts/skin.js +++ b/resources/skins.citizen.scripts/skin.js @@ -50,14 +50,12 @@ function main( window ) { search = require( './search.js' ), dropdown = require( './dropdown.js' ), setupObservers = require( './setupObservers.js' ), - stickyHeader = require( './stickyHeader.js' ), lastModified = require( './lastModified.js' ), share = require( './share.js' ); dropdown.init(); search.init( window ); echo(); - stickyHeader.init(); lastModified.init(); share.init(); diff --git a/resources/skins.citizen.scripts/stickyHeader.js b/resources/skins.citizen.scripts/stickyHeader.js index 65c09a0e..da5a3956 100644 --- a/resources/skins.citizen.scripts/stickyHeader.js +++ b/resources/skins.citizen.scripts/stickyHeader.js @@ -1,25 +1,5 @@ -const SCROLL_DOWN_CLASS = 'citizen-scroll--down'; -const SCROLL_UP_CLASS = 'citizen-scroll--up'; const STICKY_CLASS = 'citizen-page-header--sticky'; -const { initDirectionObserver, initScrollObserver } = require( './scrollObserver.js' ); - -/** - * Observes the scroll direction and adds/removes corresponding classes to the body element. - * - * @return {void} - */ -function observeScrollDirection() { - const toggleScrollClass = ( removeClass, addClass ) => () => { - window.requestAnimationFrame( () => { - document.body.classList.remove( removeClass ); - document.body.classList.add( addClass ); - } ); - }; - const addScrollDownClass = toggleScrollClass( SCROLL_UP_CLASS, SCROLL_DOWN_CLASS ); - const addScrollUpClass = toggleScrollClass( SCROLL_DOWN_CLASS, SCROLL_UP_CLASS ); - - initDirectionObserver( addScrollDownClass, addScrollUpClass, 50 ); -} +const { initScrollObserver } = require( './scrollObserver.js' ); /** * Initializes the sticky header functionality for Citizen @@ -27,13 +7,6 @@ function observeScrollDirection() { * @return {void} */ function init() { - const sentinel = document.getElementById( 'citizen-page-header-sticky-sentinel' ); - const shouldStickyHeader = getComputedStyle( sentinel ).getPropertyValue( 'display' ) !== 'none'; - if ( !shouldStickyHeader ) { - return; - } - - observeScrollDirection(); const header = document.querySelector( '.citizen-page-header' ); const placeholder = document.getElementById( 'citizen-page-header-sticky-placeholder' ); let staticHeaderHeight = header.getBoundingClientRect().height; @@ -89,6 +62,7 @@ function init() { window.removeEventListener( 'resize', onResizeEnd ); } ); + const sentinel = document.getElementById( 'citizen-page-header-sticky-sentinel' ); observer.observe( sentinel ); }