VOOZH about

URL: https://minecraft.wiki/w/MediaWiki:Gadget-sectionObserver.js

⇱ MediaWiki:Gadget-sectionObserver.js – Minecraft Wiki


MediaWiki:Gadget-sectionObserver.js

From Minecraft Wiki
Jump to navigation Jump to search
In other languages

Note: After saving, you have to bypass your browser's cache to see the changes.

Google Chrome, Firefox, Microsoft Edge, and Safari: Hold down the key and click the Reload toolbar button.
For details and instructions about other browsers, see Wikipedia:Bypass your cache.

// Source: https://github.com/wikimedia/mediawiki-skins-Vector/blob/2005cf120a871051eda6c51c79c0a533dc1a101a/resources/skins.vector.js/sectionObserver.js
/** @module SectionObserver */
/**
 * @callback OnIntersection
 * @param {HTMLElement} element The section that triggered the new intersection change.
 */
/**
 * @typedef {Object} SectionObserverProps
 * @property {NodeList} elements A list of HTML elements to observe for
 * intersection changes. This list can be updated through the `elements` setter.
 * @property {OnIntersection} onIntersection Called when a new intersection is observed.
 * @property {number} [topMargin] The number of pixels to shrink the top of
 * the viewport's bounding box before calculating intersections. This is useful
 * for sticky elements (e.g. sticky headers). Defaults to 0 pixels.
 * @property {number} [throttleMs] The number of milliseconds that the scroll
 * handler should be throttled.
 */
/**
 * @callback initSectionObserver
 * @param {SectionObserverProps} props
 * @return {SectionObserver}
 */
/**
 * Observe intersection changes with the viewport for one or more elements. This
 * is intended to be used with the headings in the content so that the
 * corresponding section(s) in the table of contents can be "activated" (e.g.
 * bolded).
 *
 * When sectionObserver notices a new intersection change, the
 * `props.onIntersection` callback will be fired with the corresponding section
 * as a param.
 *
 * Because sectionObserver uses a scroll event listener (in combination with
 * IntersectionObserver), the changes are throttled to a default maximum rate of
 * 200ms so that the main thread is not excessively blocked.
 * IntersectionObserver is used to asynchronously calculate the positions of the
 * observed tags off the main thread and in a manner that does not cause
 * expensive forced synchronous layouts.
 *
 * @param {SectionObserverProps} props
 * @return {SectionObserver}
 */
module.exports=functionsectionObserver(props){
props=Object.assign({
topMargin:0,
throttleMs:200,
onIntersection:()=>{}
},props);
let/** @type {number | undefined} */timeoutId;
let/** @type {HTMLElement | undefined} */current;
constobserver=newIntersectionObserver((entries)=>{
let/** @type {IntersectionObserverEntry | undefined} */closestNegativeEntry;
let/** @type {IntersectionObserverEntry | undefined} */closestPositiveEntry;
consttopMargin=/** @type {number} */(props.topMargin);
entries.forEach((entry)=>{
if(entry.boundingClientRect.top===0&&entry.boundingClientRect.bottom===0){
// Zero height means that it's probably a hidden heading (T330612) - ignore it
return;
}
consttop=
entry.boundingClientRect.top-topMargin;
if(
top>0&&
(
closestPositiveEntry===undefined||
top<closestPositiveEntry.boundingClientRect.top-topMargin
)
){
closestPositiveEntry=entry;
}
if(
top<=0&&
(
closestNegativeEntry===undefined||
top>closestNegativeEntry.boundingClientRect.top-topMargin
)
){
closestNegativeEntry=entry;
}
});
constclosestTag=
/** @type {HTMLElement | undefined} */(closestNegativeEntry?
closestNegativeEntry.target:
closestPositiveEntry?closestPositiveEntry.target:undefined);
// If the intersection is new, fire the `onIntersection` callback.
if(current!==closestTag&&closestTag){
props.onIntersection(closestTag);
}
current=closestTag;
// When finished finding the intersecting element, stop observing all
// observed elements. The scroll event handler will be responsible for
// throttling and reobserving the elements again. Because we don't have a
// wrapper element around our content headings and their children, we can't
// rely on IntersectionObserver (which is optimized to detect intersecting
// elements *within* the viewport) to reliably fire this callback without
// this manual step. Instead, we offload the work of calculating the
// position of each element in an efficient manner to IntersectionObserver,
// but do not use it to detect when a new element has entered the viewport.
observer.disconnect();
});
/**
	 * Calculate the intersection of each observed element.
	 */
functioncalcIntersection(){
// IntersectionObserver will asynchronously calculate the boundingClientRect
// of each observed element off the main thread after `observe` is called.
props.elements.forEach((element)=>{
if(!element.parentNode){
mw.log.warn('Element being observed is not in DOM',element);
return;
}
observer.observe(/** @type {HTMLElement} */(element));
});
}
functionhandleScroll(){
// Throttle the scroll event handler to fire at a rate limited by `props.throttleMs`.
if(!timeoutId){
timeoutId=window.setTimeout(()=>{
calcIntersection();
timeoutId=undefined;
},props.throttleMs);
}
}
functionbindScrollListener(){
window.addEventListener('scroll',handleScroll);
}
functionunbindScrollListener(){
window.removeEventListener('scroll',handleScroll);
}
/**
	 * Pauses intersection observation until `resume` is called.
	 */
functionpause(){
unbindScrollListener();
clearTimeout(timeoutId);
timeoutId=undefined;
// Assume current is no longer valid while paused.
current=undefined;
}
/**
	 * Resumes intersection observation.
	 */
functionresume(){
bindScrollListener();
}
/**
	 * Cleans up event listeners and intersection observer. Should be called when
	 * the observer is permanently no longer needed.
	 */
functionunmount(){
unbindScrollListener();
observer.disconnect();
}
/**
	 * Set a list of HTML elements to observe for intersection changes.
	 *
	 * @param {NodeList} list
	 */
functionsetElements(list){
props.elements=list;
}
bindScrollListener();
/**
	 * @typedef {Object} SectionObserver
	 * @property {calcIntersection} calcIntersection
	 * @property {pause} pause
	 * @property {resume} resume
	 * @property {unmount} unmount
	 * @property {setElements} setElements
	 */
return{
calcIntersection,
pause,
resume,
unmount,
setElements
};
};
Retrieved from "https://minecraft.wiki/w/MediaWiki:Gadget-sectionObserver.js?oldid=3451601"

Navigation menu