2024-07-01 17:19:31 +00:00
|
|
|
const SCROLL_DOWN_CLASS = 'citizen-scroll--down';
|
|
|
|
const SCROLL_UP_CLASS = 'citizen-scroll--up';
|
|
|
|
const STICKY_CLASS = 'citizen-page-header--sticky';
|
|
|
|
const { initDirectionObserver, initIntersectionObserver } = 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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes the sticky header functionality for Citizen
|
|
|
|
*
|
|
|
|
* @return {void}
|
|
|
|
*/
|
|
|
|
function init() {
|
|
|
|
observeScrollDirection();
|
|
|
|
|
2024-07-02 23:20:17 +00:00
|
|
|
const header = document.querySelector( '.citizen-page-header' );
|
|
|
|
const sentinel = document.createElement( 'div' );
|
|
|
|
sentinel.id = 'citizen-page-header-sticky-sentinel';
|
|
|
|
header.insertAdjacentElement( 'afterend', sentinel );
|
|
|
|
|
|
|
|
const shouldStickyHeader = getComputedStyle( sentinel ).getPropertyValue( 'display' ) !== 'none';
|
2024-07-01 17:19:31 +00:00
|
|
|
if ( !shouldStickyHeader ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-07 21:58:02 +00:00
|
|
|
const placeholder = document.createElement( 'div' );
|
|
|
|
placeholder.id = 'citizen-page-header-sticky-placeholder';
|
|
|
|
header.insertAdjacentElement( 'afterend', placeholder );
|
|
|
|
|
2024-07-07 22:17:29 +00:00
|
|
|
let staticHeaderHeight = header.getBoundingClientRect().height;
|
2024-07-19 21:08:03 +00:00
|
|
|
let stickyHeaderHeight = 0;
|
2024-07-12 07:47:25 +00:00
|
|
|
let placeholderHeight = 0;
|
|
|
|
let shouldRecalcHeight = true;
|
2024-07-07 21:58:02 +00:00
|
|
|
|
|
|
|
const toggleStickyHeader = ( isSticky ) => {
|
2024-07-01 17:19:31 +00:00
|
|
|
window.requestAnimationFrame( () => {
|
2024-07-12 07:47:25 +00:00
|
|
|
if ( !shouldRecalcHeight ) {
|
|
|
|
// The previous height is valid, set the height first
|
2024-07-19 21:08:03 +00:00
|
|
|
placeholder.style.height = isSticky ? `${ placeholderHeight }px` : '0px';
|
2024-07-12 07:47:25 +00:00
|
|
|
document.body.classList.toggle( STICKY_CLASS, isSticky );
|
|
|
|
} else {
|
|
|
|
// The previous height is invalid, need to set to sticky to get the sticky height
|
|
|
|
document.body.classList.toggle( STICKY_CLASS, isSticky );
|
2024-07-19 21:08:03 +00:00
|
|
|
if ( isSticky ) {
|
|
|
|
stickyHeaderHeight = header.getBoundingClientRect().height;
|
|
|
|
placeholderHeight = staticHeaderHeight - stickyHeaderHeight;
|
|
|
|
placeholder.style.height = `${ placeholderHeight }px`;
|
|
|
|
shouldRecalcHeight = false;
|
|
|
|
}
|
|
|
|
placeholder.style.height = `${ isSticky ? placeholderHeight : 0 }px`;
|
2024-07-12 07:47:25 +00:00
|
|
|
}
|
2024-07-19 21:08:03 +00:00
|
|
|
// Update sticky header CSS variable, used by other sticky elements
|
|
|
|
document.documentElement.style.setProperty(
|
|
|
|
'--height-sticky-header',
|
|
|
|
`${ isSticky ? stickyHeaderHeight : 0 }px`
|
|
|
|
);
|
2024-07-01 17:19:31 +00:00
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
2024-07-08 18:01:13 +00:00
|
|
|
const onResize = () => {
|
|
|
|
toggleStickyHeader( false );
|
|
|
|
};
|
|
|
|
|
|
|
|
const onResizeEnd = mw.util.debounce( () => {
|
|
|
|
// Refresh static header height after resize
|
|
|
|
staticHeaderHeight = header.getBoundingClientRect().height;
|
2024-07-12 07:47:25 +00:00
|
|
|
shouldRecalcHeight = true;
|
2024-07-08 18:01:13 +00:00
|
|
|
toggleStickyHeader( true );
|
|
|
|
}, 250 );
|
|
|
|
|
2024-07-01 17:19:31 +00:00
|
|
|
const observer = initIntersectionObserver(
|
2024-07-07 21:58:02 +00:00
|
|
|
() => {
|
|
|
|
toggleStickyHeader( true );
|
2024-07-08 18:01:13 +00:00
|
|
|
window.addEventListener( 'resize', onResize );
|
|
|
|
window.addEventListener( 'resize', onResizeEnd );
|
2024-07-07 21:58:02 +00:00
|
|
|
},
|
|
|
|
() => {
|
|
|
|
toggleStickyHeader( false );
|
2024-07-08 18:01:13 +00:00
|
|
|
window.removeEventListener( 'resize', onResize );
|
|
|
|
window.removeEventListener( 'resize', onResizeEnd );
|
2024-07-07 21:58:02 +00:00
|
|
|
}
|
2024-07-01 17:19:31 +00:00
|
|
|
);
|
|
|
|
observer.observe( sentinel );
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
init: init
|
|
|
|
};
|