Skip to content

Instantly share code, notes, and snippets.

@JPBM135
Created September 16, 2024 20:10
Show Gist options
  • Save JPBM135/78062bad0281ed5dd9f6baf020da7545 to your computer and use it in GitHub Desktop.
Save JPBM135/78062bad0281ed5dd9f6baf020da7545 to your computer and use it in GitHub Desktop.
Block Scroll and Scrollbar
/*
37 - ArrowUp
38 - ArrowDown
39 - ArrowRight
40 - ArrowLeft
32 - Space
33 - PageUp
34 - PageDown
35 - End
36 - Home
*/
const SCROLL_RELATED_KEYS = new Set([37, 38, 39, 40, 32, 33, 34, 35, 36]);
const SINGLETON_INSTANCE: {
disableScroll: (() => void) | null;
enableScroll: (() => void) | null;
} = {
disableScroll: null,
enableScroll: null,
};
// This variable is used to override the scrollbar display, see `scrollbar.scss`
const SCROLLBAR_OVERRIDE_VARIABLE =
'--override-scrollbar-display';
function checkIfBrowserSupportsPassive() {
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#safely_detecting_option_support
let passiveSupported = false;
try {
const options = {
get passive() {
// This function will be called when the browser
// attempts to access the passive property.
passiveSupported = true;
return false;
},
} as EventListenerOptions;
window.addEventListener(
'test' as unknown as keyof WindowEventMap,
null as unknown as EventListener,
options,
);
window.removeEventListener(
'test' as unknown as keyof WindowEventMap,
null as unknown as EventListener,
options,
);
} catch (err) {
passiveSupported = false;
}
return passiveSupported;
}
function determineScrollEventOptionsAndName(isPassiveSupported: boolean) {
// Chrome and Firefox on Android support passive event listeners
const scrollEventListenerOptions: EventListenerOptions | boolean =
isPassiveSupported ? ({ passive: false } as EventListenerOptions) : false;
const testDiv = document.createElement('div');
const scrollEventName = ('onwheel' in testDiv
? 'wheel'
: 'mousewheel') as unknown as keyof WindowEventMap;
// Schedule the removal of the test div for the next tick
setTimeout(() => {
testDiv.remove();
});
return {
scrollEventName,
scrollEventListenerOptions,
};
}
interface ToggleWindowEventOptions {
eventName: keyof WindowEventMap;
handler: EventListener | ((e: KeyboardEvent) => void);
options?: EventListenerOptions | boolean;
}
function toggleWindowEvents(
events: ToggleWindowEventOptions[],
type: 'add' | 'remove',
) {
for (const event of events) {
if (type === 'add') {
window.addEventListener(
event.eventName,
event.handler as EventListener,
event.options,
);
continue;
}
window.removeEventListener(
event.eventName,
event.handler as EventListener,
event.options,
);
}
}
function createScrollToggle() {
if (typeof window === 'undefined') {
throw new Error('This function should be called in the browser');
}
// Helper function for disabling scroll
const preventDefault = (e: Event) => {
e.preventDefault();
};
const preventDefaultForScrollKeys = (e: KeyboardEvent) => {
if (!SCROLL_RELATED_KEYS.has(Number(e.code))) return;
e.preventDefault();
};
const passiveSupported = checkIfBrowserSupportsPassive();
const { scrollEventName, scrollEventListenerOptions } =
determineScrollEventOptionsAndName(passiveSupported);
const WINDOW_EVENTS: ToggleWindowEventOptions[] = [
{
eventName: 'DOMMouseScroll' as unknown as keyof WindowEventMap,
handler: preventDefault,
options: false,
},
{
eventName: scrollEventName,
handler: preventDefault,
options: scrollEventListenerOptions,
},
{
eventName: 'touchmove',
handler: preventDefault,
options: scrollEventListenerOptions,
},
{
eventName: 'keydown',
handler: preventDefaultForScrollKeys,
},
];
const disableScroll = () => {
toggleWindowEvents(WINDOW_EVENTS, 'add');
document.documentElement.style.setProperty(
PALANTIR_SCROLLBAR_OVERRIDE_VARIABLE,
'none',
);
};
// call this to Enable
const enableScroll = () => {
toggleWindowEvents(WINDOW_EVENTS, 'remove');
document.documentElement.style.removeProperty(
PALANTIR_SCROLLBAR_OVERRIDE_VARIABLE,
);
};
return {
disableScroll,
enableScroll,
};
}
export function toggleScroll() {
if (!SINGLETON_INSTANCE.disableScroll) {
const { disableScroll, enableScroll } = createScrollToggle();
SINGLETON_INSTANCE.disableScroll = disableScroll;
SINGLETON_INSTANCE.enableScroll = enableScroll;
}
return SINGLETON_INSTANCE as {
disableScroll: () => void;
enableScroll: () => void;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment