mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 08:00:36 +03:00
add drag scrolling & improve code
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
import type { StructureSetupState } from 'setups';
|
||||||
|
|
||||||
|
const { min, max } = Math;
|
||||||
|
export const getScrollbarHandleLengthRatio = (
|
||||||
|
structureSetupState: StructureSetupState,
|
||||||
|
isHorizontal?: boolean
|
||||||
|
) => {
|
||||||
|
const { _overflowAmount, _overflowEdge } = structureSetupState;
|
||||||
|
const axis = isHorizontal ? 'x' : 'y';
|
||||||
|
const viewportSize = _overflowEdge[axis];
|
||||||
|
const overflowAmount = _overflowAmount[axis];
|
||||||
|
return max(0, min(1, viewportSize / (viewportSize + overflowAmount)));
|
||||||
|
};
|
||||||
|
export const getScrollbarHandleOffsetRatio = (
|
||||||
|
structureSetupState: StructureSetupState,
|
||||||
|
scrollOffsetElement: HTMLElement,
|
||||||
|
isHorizontal?: boolean
|
||||||
|
) => {
|
||||||
|
const axis = isHorizontal ? 'x' : 'y';
|
||||||
|
const scrollLeftTop = isHorizontal ? 'Left' : 'Top';
|
||||||
|
const lengthRatio = getScrollbarHandleLengthRatio(structureSetupState, isHorizontal);
|
||||||
|
const scrollPosition = scrollOffsetElement[`scroll${scrollLeftTop}`] as number;
|
||||||
|
const scrollPositionMax = Math.floor(structureSetupState._overflowAmount[axis]);
|
||||||
|
const scrollPercent = min(1, scrollPosition / scrollPositionMax);
|
||||||
|
|
||||||
|
return (1 / lengthRatio) * (1 - lengthRatio) * scrollPercent;
|
||||||
|
};
|
||||||
@@ -3,15 +3,13 @@ import {
|
|||||||
appendChildren,
|
appendChildren,
|
||||||
createDiv,
|
createDiv,
|
||||||
each,
|
each,
|
||||||
|
isBoolean,
|
||||||
isEmptyArray,
|
isEmptyArray,
|
||||||
noop,
|
|
||||||
on,
|
|
||||||
push,
|
push,
|
||||||
removeClass,
|
removeClass,
|
||||||
removeElements,
|
removeElements,
|
||||||
runEachAndClear,
|
runEachAndClear,
|
||||||
setT,
|
setT,
|
||||||
stopPropagation,
|
|
||||||
style,
|
style,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import {
|
import {
|
||||||
@@ -20,18 +18,18 @@ import {
|
|||||||
classNameScrollbarVertical,
|
classNameScrollbarVertical,
|
||||||
classNameScrollbarTrack,
|
classNameScrollbarTrack,
|
||||||
classNameScrollbarHandle,
|
classNameScrollbarHandle,
|
||||||
classNamesScrollbarInteraction,
|
|
||||||
classNamesScrollbarTransitionless,
|
classNamesScrollbarTransitionless,
|
||||||
} from 'classnames';
|
} from 'classnames';
|
||||||
import { getEnvironment } from 'environment';
|
import { getEnvironment } from 'environment';
|
||||||
import { dynamicInitializationElement as generalDynamicInitializationElement } from 'initialization';
|
import { dynamicInitializationElement as generalDynamicInitializationElement } from 'initialization';
|
||||||
import type { InitializationTarget } from 'initialization';
|
import type { InitializationTarget } from 'initialization';
|
||||||
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
|
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
|
||||||
|
import type { ScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
|
||||||
import type {
|
import type {
|
||||||
ScrollbarsInitialization,
|
ScrollbarsInitialization,
|
||||||
ScrollbarsDynamicInitializationElement,
|
ScrollbarsDynamicInitializationElement,
|
||||||
} from 'setups/scrollbarsSetup/scrollbarsSetup.initialization';
|
} from 'setups/scrollbarsSetup/scrollbarsSetup.initialization';
|
||||||
import { StyleObject } from 'typings';
|
import type { StyleObject } from 'typings';
|
||||||
|
|
||||||
export interface ScrollbarStructure {
|
export interface ScrollbarStructure {
|
||||||
_scrollbar: HTMLElement;
|
_scrollbar: HTMLElement;
|
||||||
@@ -42,24 +40,19 @@ export interface ScrollbarStructure {
|
|||||||
export interface ScrollbarsSetupElement {
|
export interface ScrollbarsSetupElement {
|
||||||
_scrollbarStructures: ScrollbarStructure[];
|
_scrollbarStructures: ScrollbarStructure[];
|
||||||
_clone: () => ScrollbarStructure;
|
_clone: () => ScrollbarStructure;
|
||||||
_addRemoveClass: (
|
|
||||||
classNames: string | false | null | undefined,
|
|
||||||
add?: boolean,
|
|
||||||
elm?: (scrollbarStructure: ScrollbarStructure) => HTMLElement | false | null | undefined
|
|
||||||
) => void;
|
|
||||||
_handleStyle: (
|
_handleStyle: (
|
||||||
elmStyle: (
|
elmStyle: (
|
||||||
scrollbarStructure: ScrollbarStructure
|
scrollbarStructure: ScrollbarStructure
|
||||||
) => [HTMLElement | false | null | undefined, StyleObject]
|
) => [HTMLElement | false | null | undefined, StyleObject]
|
||||||
) => void;
|
) => void;
|
||||||
// _removeClass: (classNames: string) => void;
|
|
||||||
/*
|
|
||||||
_addEventListener: () => void;
|
|
||||||
_removeEventListener: () => void;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScrollbarsSetupElementsObj {
|
export interface ScrollbarsSetupElementsObj {
|
||||||
|
_scrollbarsAddRemoveClass: (
|
||||||
|
classNames: string | false | null | undefined,
|
||||||
|
add?: boolean,
|
||||||
|
isHorizontal?: boolean
|
||||||
|
) => void;
|
||||||
_horizontal: ScrollbarsSetupElement;
|
_horizontal: ScrollbarsSetupElement;
|
||||||
_vertical: ScrollbarsSetupElement;
|
_vertical: ScrollbarsSetupElement;
|
||||||
}
|
}
|
||||||
@@ -70,23 +63,15 @@ export type ScrollbarsSetupElements = [
|
|||||||
destroy: () => void
|
destroy: () => void
|
||||||
];
|
];
|
||||||
|
|
||||||
const interactionStartEventNames = 'touchstart mouseenter';
|
|
||||||
const interactionEndEventNames = 'touchend touchcancel mouseleave';
|
|
||||||
const stopRootClickPropagation = (scrollbar: HTMLElement, documentElm: Document) =>
|
|
||||||
on(
|
|
||||||
scrollbar,
|
|
||||||
'mousedown',
|
|
||||||
on.bind(0, documentElm, 'click', stopPropagation, { _once: true, _capture: true }),
|
|
||||||
{ _capture: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
export const createScrollbarsSetupElements = (
|
export const createScrollbarsSetupElements = (
|
||||||
target: InitializationTarget,
|
target: InitializationTarget,
|
||||||
structureSetupElements: StructureSetupElementsObj
|
structureSetupElements: StructureSetupElementsObj,
|
||||||
|
scrollbarsSetupEvents: ScrollbarsSetupEvents
|
||||||
): ScrollbarsSetupElements => {
|
): ScrollbarsSetupElements => {
|
||||||
const { _getDefaultInitialization } = getEnvironment();
|
const { _getDefaultInitialization } = getEnvironment();
|
||||||
const { scrollbarsSlot: defaultScrollbarsSlot } = _getDefaultInitialization();
|
const { scrollbarsSlot: defaultScrollbarsSlot } = _getDefaultInitialization();
|
||||||
const { _documentElm, _target, _host, _viewport, _targetIsElm } = structureSetupElements;
|
const { _documentElm, _target, _host, _viewport, _targetIsElm, _scrollOffsetElement } =
|
||||||
|
structureSetupElements;
|
||||||
const { scrollbarsSlot } = (_targetIsElm ? {} : target) as ScrollbarsInitialization;
|
const { scrollbarsSlot } = (_targetIsElm ? {} : target) as ScrollbarsInitialization;
|
||||||
const evaluatedScrollbarSlot =
|
const evaluatedScrollbarSlot =
|
||||||
generalDynamicInitializationElement<ScrollbarsDynamicInitializationElement>(
|
generalDynamicInitializationElement<ScrollbarsDynamicInitializationElement>(
|
||||||
@@ -95,15 +80,14 @@ export const createScrollbarsSetupElements = (
|
|||||||
defaultScrollbarsSlot,
|
defaultScrollbarsSlot,
|
||||||
scrollbarsSlot
|
scrollbarsSlot
|
||||||
);
|
);
|
||||||
const scrollbarsAddRemoveClass = (
|
const scrollbarStructureAddRemoveClass = (
|
||||||
scrollbarStructures: ScrollbarStructure[],
|
scrollbarStructures: ScrollbarStructure[],
|
||||||
classNames: string | false | null | undefined,
|
classNames: string | false | null | undefined,
|
||||||
add?: boolean,
|
add?: boolean
|
||||||
elm?: (scrollbarStructure: ScrollbarStructure) => HTMLElement | false | null | undefined
|
|
||||||
) => {
|
) => {
|
||||||
const action = add ? addClass : removeClass;
|
const action = add ? addClass : removeClass;
|
||||||
each(scrollbarStructures, (scrollbarStructure) => {
|
each(scrollbarStructures, (scrollbarStructure) => {
|
||||||
action((elm || noop)(scrollbarStructure) || scrollbarStructure._scrollbar, classNames);
|
action(scrollbarStructure._scrollbar, classNames);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const scrollbarsHandleStyle = (
|
const scrollbarsHandleStyle = (
|
||||||
@@ -121,13 +105,22 @@ export const createScrollbarsSetupElements = (
|
|||||||
const horizontalScrollbars: ScrollbarStructure[] = [];
|
const horizontalScrollbars: ScrollbarStructure[] = [];
|
||||||
const verticalScrollbars: ScrollbarStructure[] = [];
|
const verticalScrollbars: ScrollbarStructure[] = [];
|
||||||
|
|
||||||
const addRemoveClassHorizontal = scrollbarsAddRemoveClass.bind(0, horizontalScrollbars);
|
const scrollbarsAddRemoveClass = (
|
||||||
const addRemoveClassVertical = scrollbarsAddRemoveClass.bind(0, verticalScrollbars);
|
className: string | false | null | undefined,
|
||||||
const generateScrollbarDOM = (horizontal?: boolean): ScrollbarStructure => {
|
add?: boolean,
|
||||||
const scrollbarClassName = horizontal
|
onlyHorizontal?: boolean
|
||||||
|
) => {
|
||||||
|
const singleAxis = isBoolean(onlyHorizontal);
|
||||||
|
const runHorizontal = singleAxis ? onlyHorizontal : true;
|
||||||
|
const runVertical = singleAxis ? !onlyHorizontal : true;
|
||||||
|
runHorizontal && scrollbarStructureAddRemoveClass(horizontalScrollbars, className, add);
|
||||||
|
runVertical && scrollbarStructureAddRemoveClass(verticalScrollbars, className, add);
|
||||||
|
};
|
||||||
|
const generateScrollbarDOM = (isHorizontal?: boolean): ScrollbarStructure => {
|
||||||
|
const scrollbarClassName = isHorizontal
|
||||||
? classNameScrollbarHorizontal
|
? classNameScrollbarHorizontal
|
||||||
: classNameScrollbarVertical;
|
: classNameScrollbarVertical;
|
||||||
const arrToPush = horizontal ? horizontalScrollbars : verticalScrollbars;
|
const arrToPush = isHorizontal ? horizontalScrollbars : verticalScrollbars;
|
||||||
const transitionlessClass = isEmptyArray(arrToPush) ? classNamesScrollbarTransitionless : '';
|
const transitionlessClass = isEmptyArray(arrToPush) ? classNamesScrollbarTransitionless : '';
|
||||||
const scrollbar = createDiv(
|
const scrollbar = createDiv(
|
||||||
`${classNameScrollbar} ${scrollbarClassName} ${transitionlessClass}`
|
`${classNameScrollbar} ${scrollbarClassName} ${transitionlessClass}`
|
||||||
@@ -146,15 +139,13 @@ export const createScrollbarsSetupElements = (
|
|||||||
push(arrToPush, result);
|
push(arrToPush, result);
|
||||||
push(destroyFns, [
|
push(destroyFns, [
|
||||||
removeElements.bind(0, scrollbar),
|
removeElements.bind(0, scrollbar),
|
||||||
on(scrollbar, interactionStartEventNames, () => {
|
scrollbarsSetupEvents(
|
||||||
addRemoveClassHorizontal(classNamesScrollbarInteraction, true);
|
result,
|
||||||
addRemoveClassVertical(classNamesScrollbarInteraction, true);
|
scrollbarsAddRemoveClass,
|
||||||
}),
|
_documentElm,
|
||||||
on(scrollbar, interactionEndEventNames, () => {
|
_scrollOffsetElement,
|
||||||
addRemoveClassHorizontal(classNamesScrollbarInteraction);
|
isHorizontal
|
||||||
addRemoveClassVertical(classNamesScrollbarInteraction);
|
),
|
||||||
}),
|
|
||||||
stopRootClickPropagation(scrollbar, _documentElm),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -166,8 +157,7 @@ export const createScrollbarsSetupElements = (
|
|||||||
appendChildren(evaluatedScrollbarSlot, verticalScrollbars[0]._scrollbar);
|
appendChildren(evaluatedScrollbarSlot, verticalScrollbars[0]._scrollbar);
|
||||||
|
|
||||||
setT(() => {
|
setT(() => {
|
||||||
addRemoveClassHorizontal(classNamesScrollbarTransitionless);
|
scrollbarsAddRemoveClass(classNamesScrollbarTransitionless);
|
||||||
addRemoveClassVertical(classNamesScrollbarTransitionless);
|
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,16 +166,15 @@ export const createScrollbarsSetupElements = (
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
_scrollbarsAddRemoveClass: scrollbarsAddRemoveClass,
|
||||||
_horizontal: {
|
_horizontal: {
|
||||||
_scrollbarStructures: horizontalScrollbars,
|
_scrollbarStructures: horizontalScrollbars,
|
||||||
_clone: generateHorizontalScrollbarStructure,
|
_clone: generateHorizontalScrollbarStructure,
|
||||||
_addRemoveClass: addRemoveClassHorizontal,
|
|
||||||
_handleStyle: scrollbarsHandleStyle.bind(0, horizontalScrollbars),
|
_handleStyle: scrollbarsHandleStyle.bind(0, horizontalScrollbars),
|
||||||
},
|
},
|
||||||
_vertical: {
|
_vertical: {
|
||||||
_scrollbarStructures: verticalScrollbars,
|
_scrollbarStructures: verticalScrollbars,
|
||||||
_clone: generateVerticalScrollbarStructure,
|
_clone: generateVerticalScrollbarStructure,
|
||||||
_addRemoveClass: addRemoveClassVertical,
|
|
||||||
_handleStyle: scrollbarsHandleStyle.bind(0, verticalScrollbars),
|
_handleStyle: scrollbarsHandleStyle.bind(0, verticalScrollbars),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
getBoundingClientRect,
|
||||||
|
offsetSize,
|
||||||
|
on,
|
||||||
|
preventDefault,
|
||||||
|
runEachAndClear,
|
||||||
|
stopPropagation,
|
||||||
|
XY,
|
||||||
|
} from 'support';
|
||||||
|
import { classNamesScrollbarInteraction } from 'classnames';
|
||||||
|
import { getScrollbarHandleOffsetRatio } from 'setups/scrollbarsSetup/scrollbarsSetup.calculations';
|
||||||
|
import type { StructureSetupState } from 'setups';
|
||||||
|
import type {
|
||||||
|
ScrollbarsSetupElementsObj,
|
||||||
|
ScrollbarStructure,
|
||||||
|
} from 'setups/scrollbarsSetup/scrollbarsSetup.elements';
|
||||||
|
|
||||||
|
export type ScrollbarsSetupEvents = (
|
||||||
|
scrollbarStructure: ScrollbarStructure,
|
||||||
|
scrollbarsAddRemoveClass: ScrollbarsSetupElementsObj['_scrollbarsAddRemoveClass'],
|
||||||
|
documentElm: Document,
|
||||||
|
scrollOffsetElm: HTMLElement,
|
||||||
|
isHorizontal?: boolean
|
||||||
|
) => () => void;
|
||||||
|
|
||||||
|
const getPageOffset = (event: PointerEvent): XY<number> => ({
|
||||||
|
x: event.pageX,
|
||||||
|
y: event.pageY,
|
||||||
|
});
|
||||||
|
const getInvertedScale = (element: HTMLElement): XY<number> => {
|
||||||
|
const { width, height } = getBoundingClientRect(element);
|
||||||
|
const { w, h } = offsetSize(element);
|
||||||
|
return {
|
||||||
|
x: 1 / (Math.round(width) / w) || 1,
|
||||||
|
y: 1 / (Math.round(height) / h) || 1,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const createRootClickStopPropagationEvents = (scrollbar: HTMLElement, documentElm: Document) =>
|
||||||
|
on(
|
||||||
|
scrollbar,
|
||||||
|
'mousedown',
|
||||||
|
on.bind(0, documentElm, 'click', stopPropagation, { _once: true, _capture: true }),
|
||||||
|
{ _capture: true }
|
||||||
|
);
|
||||||
|
const createDragScrollingEvents = (
|
||||||
|
doc: Document,
|
||||||
|
scrollbarHandle: HTMLElement,
|
||||||
|
scrollOffsetElement: HTMLElement,
|
||||||
|
structureSetupState: () => StructureSetupState,
|
||||||
|
isHorizontal?: boolean
|
||||||
|
) => {
|
||||||
|
const scrollOffsetKey = `scroll${isHorizontal ? 'Left' : 'Top'}`;
|
||||||
|
const xyKey = `${isHorizontal ? 'x' : 'y'}`;
|
||||||
|
const createOnPointerMoveHandler =
|
||||||
|
(mouseDownScroll: number, mouseDownPageOffset: number, mouseDownInvertedScale: number) =>
|
||||||
|
(event: PointerEvent) => {
|
||||||
|
const movement = (getPageOffset(event)[xyKey] - mouseDownPageOffset) * mouseDownInvertedScale;
|
||||||
|
const handleLengthRatio =
|
||||||
|
1 / getScrollbarHandleOffsetRatio(structureSetupState(), scrollOffsetElement, isHorizontal);
|
||||||
|
scrollOffsetElement[scrollOffsetKey] = mouseDownScroll + movement * handleLengthRatio;
|
||||||
|
// if (_isRTL && isHorizontal && !_rtlScrollBehavior.i) scrollDelta *= -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
return on(scrollbarHandle, 'pointerdown', (pointerDownEvent: PointerEvent) => {
|
||||||
|
const { button, isPrimary, pointerId } = pointerDownEvent;
|
||||||
|
|
||||||
|
if (button === 0 && isPrimary) {
|
||||||
|
const mouseDownScroll = scrollOffsetElement[scrollOffsetKey] || 0;
|
||||||
|
const mouseDownPageOffset = getPageOffset(pointerDownEvent)[xyKey];
|
||||||
|
const mouseDownInvertedScale = getInvertedScale(scrollOffsetElement)[xyKey];
|
||||||
|
const offSelectStart = on(doc, 'selectstart', (event: Event) => preventDefault(event), {
|
||||||
|
_passive: false,
|
||||||
|
});
|
||||||
|
const offPointerMove = on(
|
||||||
|
scrollbarHandle,
|
||||||
|
'pointermove',
|
||||||
|
createOnPointerMoveHandler(mouseDownScroll, mouseDownPageOffset, mouseDownInvertedScale)
|
||||||
|
);
|
||||||
|
|
||||||
|
on(
|
||||||
|
scrollbarHandle,
|
||||||
|
'pointerup',
|
||||||
|
(pointerUpEvent: PointerEvent) => {
|
||||||
|
offSelectStart();
|
||||||
|
offPointerMove();
|
||||||
|
scrollbarHandle.releasePointerCapture(pointerUpEvent.pointerId);
|
||||||
|
},
|
||||||
|
{ _once: true }
|
||||||
|
);
|
||||||
|
scrollbarHandle.setPointerCapture(pointerId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createScrollbarsSetupEvents =
|
||||||
|
(structureSetupState: () => StructureSetupState): ScrollbarsSetupEvents =>
|
||||||
|
(scrollbarStructure, scrollbarsAddRemoveClass, documentElm, scrollOffsetElm, isHorizontal) => {
|
||||||
|
const { _scrollbar, _handle } = scrollbarStructure;
|
||||||
|
|
||||||
|
return runEachAndClear.bind(0, [
|
||||||
|
on(_scrollbar, 'pointerenter', () => {
|
||||||
|
scrollbarsAddRemoveClass(classNamesScrollbarInteraction, true);
|
||||||
|
}),
|
||||||
|
on(_scrollbar, 'pointerleave pointercancel', () => {
|
||||||
|
scrollbarsAddRemoveClass(classNamesScrollbarInteraction);
|
||||||
|
}),
|
||||||
|
createRootClickStopPropagationEvents(_scrollbar, documentElm),
|
||||||
|
createDragScrollingEvents(
|
||||||
|
documentElm,
|
||||||
|
_handle,
|
||||||
|
scrollOffsetElm,
|
||||||
|
structureSetupState,
|
||||||
|
isHorizontal
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
};
|
||||||
@@ -11,6 +11,11 @@ import {
|
|||||||
scrollTop,
|
scrollTop,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import { createState, createOptionCheck } from 'setups/setups';
|
import { createState, createOptionCheck } from 'setups/setups';
|
||||||
|
import {
|
||||||
|
getScrollbarHandleLengthRatio,
|
||||||
|
getScrollbarHandleOffsetRatio,
|
||||||
|
} from 'setups/scrollbarsSetup/scrollbarsSetup.calculations';
|
||||||
|
import { createScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
|
||||||
import {
|
import {
|
||||||
createScrollbarsSetupElements,
|
createScrollbarsSetupElements,
|
||||||
ScrollbarsSetupElement,
|
ScrollbarsSetupElement,
|
||||||
@@ -40,7 +45,6 @@ export interface ScrollbarsSetupStaticState {
|
|||||||
_appendElements: () => void;
|
_appendElements: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { min } = Math;
|
|
||||||
const createSelfCancelTimeout = (timeout?: number | (() => number)) => {
|
const createSelfCancelTimeout = (timeout?: number | (() => number)) => {
|
||||||
let id: number;
|
let id: number;
|
||||||
const setTFn = timeout ? setT : rAF!;
|
const setTFn = timeout ? setT : rAF!;
|
||||||
@@ -55,17 +59,6 @@ const createSelfCancelTimeout = (timeout?: number | (() => number)) => {
|
|||||||
] as [timeout: (callback: () => any) => void, clear: () => void];
|
] as [timeout: (callback: () => any) => void, clear: () => void];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getScrollbarHandleRatio = (
|
|
||||||
structureSetupState: StructureSetupState,
|
|
||||||
isHorizontal?: boolean
|
|
||||||
) => {
|
|
||||||
const { _overflowAmount, _overflowEdge } = structureSetupState;
|
|
||||||
const axis = isHorizontal ? 'x' : 'y';
|
|
||||||
const viewportSize = _overflowEdge[axis];
|
|
||||||
const overflowAmount = _overflowAmount[axis];
|
|
||||||
return min(1, viewportSize / (viewportSize + overflowAmount));
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshScrollbarHandleLength = (
|
const refreshScrollbarHandleLength = (
|
||||||
setStyleFn: ScrollbarsSetupElement['_handleStyle'],
|
setStyleFn: ScrollbarsSetupElement['_handleStyle'],
|
||||||
structureSetupState: StructureSetupState,
|
structureSetupState: StructureSetupState,
|
||||||
@@ -75,7 +68,7 @@ const refreshScrollbarHandleLength = (
|
|||||||
structure._handle,
|
structure._handle,
|
||||||
{
|
{
|
||||||
[isHorizontal ? 'width' : 'height']: `${(
|
[isHorizontal ? 'width' : 'height']: `${(
|
||||||
getScrollbarHandleRatio(structureSetupState, isHorizontal) * 100
|
getScrollbarHandleLengthRatio(structureSetupState, isHorizontal) * 100
|
||||||
).toFixed(3)}%`,
|
).toFixed(3)}%`,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -83,28 +76,23 @@ const refreshScrollbarHandleLength = (
|
|||||||
const refreshScrollbarHandleOffset = (
|
const refreshScrollbarHandleOffset = (
|
||||||
setStyleFn: ScrollbarsSetupElement['_handleStyle'],
|
setStyleFn: ScrollbarsSetupElement['_handleStyle'],
|
||||||
structureSetupState: StructureSetupState,
|
structureSetupState: StructureSetupState,
|
||||||
viewport: HTMLElement,
|
scrollOffsetElement: HTMLElement,
|
||||||
isHorizontal?: boolean
|
isHorizontal?: boolean
|
||||||
) => {
|
) => {
|
||||||
const axis = isHorizontal ? 'x' : 'y';
|
|
||||||
const translateAxis = isHorizontal ? 'X' : 'Y';
|
const translateAxis = isHorizontal ? 'X' : 'Y';
|
||||||
const scrollLeftTop = isHorizontal ? 'Left' : 'Top';
|
const offsetRatio = getScrollbarHandleOffsetRatio(
|
||||||
const handleRatio = getScrollbarHandleRatio(structureSetupState, isHorizontal);
|
structureSetupState,
|
||||||
const scrollPosition = viewport[`scroll${scrollLeftTop}`] as number;
|
scrollOffsetElement,
|
||||||
const scrollPositionMax =
|
isHorizontal
|
||||||
(viewport[`scroll${scrollLeftTop}Max`] as number) ||
|
);
|
||||||
Math.floor(structureSetupState._overflowAmount[axis]);
|
// eslint-disable-next-line no-self-compare
|
||||||
|
const validOffsetRatio = offsetRatio === offsetRatio; // is false when offset is NaN
|
||||||
|
|
||||||
setStyleFn((structure) => [
|
setStyleFn((structure) => [
|
||||||
structure._handle,
|
structure._handle,
|
||||||
{
|
{
|
||||||
transform: scrollPositionMax
|
transform: validOffsetRatio
|
||||||
? `translate${translateAxis}(${(
|
? `translate${translateAxis}(${(offsetRatio * 100).toFixed(3)}%)`
|
||||||
(1 / handleRatio) *
|
|
||||||
(1 - handleRatio) *
|
|
||||||
(scrollPosition / scrollPositionMax) *
|
|
||||||
100
|
|
||||||
).toFixed(3)}%)`
|
|
||||||
: '',
|
: '',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -131,34 +119,38 @@ export const createScrollbarsSetup = (
|
|||||||
const [auotHideTimeout, clearAutoTimeout] = createSelfCancelTimeout(() => globalAutoHideDelay);
|
const [auotHideTimeout, clearAutoTimeout] = createSelfCancelTimeout(() => globalAutoHideDelay);
|
||||||
const [elements, appendElements, destroyElements] = createScrollbarsSetupElements(
|
const [elements, appendElements, destroyElements] = createScrollbarsSetupElements(
|
||||||
target,
|
target,
|
||||||
structureSetupState._elements
|
structureSetupState._elements,
|
||||||
|
createScrollbarsSetupEvents(structureSetupState)
|
||||||
);
|
);
|
||||||
const { _host, _viewport, _viewportIsTarget, _isBody, _documentElm } =
|
const {
|
||||||
structureSetupState._elements;
|
_host,
|
||||||
const scrollOffsetElement = _isBody ? _documentElm.documentElement : _viewport;
|
_viewport,
|
||||||
const { _horizontal, _vertical } = elements;
|
_scrollOffsetElement,
|
||||||
const { _addRemoveClass: addRemoveClassHorizontal, _handleStyle: styleHorizontal } = _horizontal;
|
_scrollEventElement,
|
||||||
const { _addRemoveClass: addRemoveClassVertical, _handleStyle: styleVertical } = _vertical;
|
_viewportIsTarget,
|
||||||
|
_isBody,
|
||||||
|
} = structureSetupState._elements;
|
||||||
|
const { _horizontal, _vertical, _scrollbarsAddRemoveClass: scrollbarsAddRemoveClass } = elements;
|
||||||
|
const { _handleStyle: styleHorizontal } = _horizontal;
|
||||||
|
const { _handleStyle: styleVertical } = _vertical;
|
||||||
const styleScrollbarPosition = (structure: ScrollbarStructure) => {
|
const styleScrollbarPosition = (structure: ScrollbarStructure) => {
|
||||||
const { _scrollbar } = structure;
|
const { _scrollbar } = structure;
|
||||||
const elm = _viewportIsTarget && !_isBody && parent(_scrollbar) === _viewport && _scrollbar;
|
const elm = _viewportIsTarget && !_isBody && parent(_scrollbar) === _viewport && _scrollbar;
|
||||||
return [
|
return [
|
||||||
elm,
|
elm,
|
||||||
{
|
{
|
||||||
transform: elm ? `translate(${scrollLeft(_viewport)}px, ${scrollTop(_viewport)}px)` : '',
|
transform: elm
|
||||||
|
? `translate(${scrollLeft(_scrollOffsetElement)}px, ${scrollTop(_scrollOffsetElement)}px)`
|
||||||
|
: '',
|
||||||
},
|
},
|
||||||
] as [HTMLElement | false, StyleObject];
|
] as [HTMLElement | false, StyleObject];
|
||||||
};
|
};
|
||||||
const manageScrollbarsAutoHide = (removeAutoHide: boolean, delayless?: boolean) => {
|
const manageScrollbarsAutoHide = (removeAutoHide: boolean, delayless?: boolean) => {
|
||||||
clearAutoTimeout();
|
clearAutoTimeout();
|
||||||
if (removeAutoHide) {
|
if (removeAutoHide) {
|
||||||
addRemoveClassHorizontal(classNamesScrollbarAutoHidden);
|
scrollbarsAddRemoveClass(classNamesScrollbarAutoHidden);
|
||||||
addRemoveClassVertical(classNamesScrollbarAutoHidden);
|
|
||||||
} else {
|
} else {
|
||||||
const hide = () => {
|
const hide = () => scrollbarsAddRemoveClass(classNamesScrollbarAutoHidden, true);
|
||||||
addRemoveClassHorizontal(classNamesScrollbarAutoHidden, true);
|
|
||||||
addRemoveClassVertical(classNamesScrollbarAutoHidden, true);
|
|
||||||
};
|
|
||||||
if (globalAutoHideDelay > 0 && !delayless) {
|
if (globalAutoHideDelay > 0 && !delayless) {
|
||||||
auotHideTimeout(hide);
|
auotHideTimeout(hide);
|
||||||
} else {
|
} else {
|
||||||
@@ -195,11 +187,11 @@ export const createScrollbarsSetup = (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
on(_isBody ? _documentElm : _viewport, 'scroll', () => {
|
on(_scrollEventElement, 'scroll', () => {
|
||||||
requestScrollAnimationFrame(() => {
|
requestScrollAnimationFrame(() => {
|
||||||
const structureState = structureSetupState();
|
const structureState = structureSetupState();
|
||||||
refreshScrollbarHandleOffset(styleHorizontal, structureState, scrollOffsetElement, true);
|
refreshScrollbarHandleOffset(styleHorizontal, structureState, _scrollOffsetElement, true);
|
||||||
refreshScrollbarHandleOffset(styleVertical, structureState, scrollOffsetElement);
|
refreshScrollbarHandleOffset(styleVertical, structureState, _scrollOffsetElement);
|
||||||
|
|
||||||
autoHideNotNever && manageScrollbarsAutoHide(true);
|
autoHideNotNever && manageScrollbarsAutoHide(true);
|
||||||
scrollTimeout(() => {
|
scrollTimeout(() => {
|
||||||
@@ -237,13 +229,10 @@ export const createScrollbarsSetup = (
|
|||||||
const updateHandle = _overflowEdgeChanged || _overflowAmountChanged;
|
const updateHandle = _overflowEdgeChanged || _overflowAmountChanged;
|
||||||
const updateVisibility = _overflowStyleChanged || visibilityChanged;
|
const updateVisibility = _overflowStyleChanged || visibilityChanged;
|
||||||
|
|
||||||
const setScrollbarVisibility = (
|
const setScrollbarVisibility = (overflowStyle: OverflowStyle, isHorizontal: boolean) => {
|
||||||
overflowStyle: OverflowStyle,
|
|
||||||
addRemoveClass: (classNames: string, add?: boolean) => void
|
|
||||||
) => {
|
|
||||||
const isVisible =
|
const isVisible =
|
||||||
visibility === 'visible' || (visibility === 'auto' && overflowStyle === 'scroll');
|
visibility === 'visible' || (visibility === 'auto' && overflowStyle === 'scroll');
|
||||||
addRemoveClass(classNamesScrollbarVisible, isVisible);
|
scrollbarsAddRemoveClass(classNamesScrollbarVisible, isVisible, isHorizontal);
|
||||||
return isVisible;
|
return isVisible;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -252,19 +241,16 @@ export const createScrollbarsSetup = (
|
|||||||
if (updateVisibility) {
|
if (updateVisibility) {
|
||||||
const { _overflowStyle } = currStructureSetupState;
|
const { _overflowStyle } = currStructureSetupState;
|
||||||
|
|
||||||
const xVisible = setScrollbarVisibility(_overflowStyle.x, addRemoveClassHorizontal);
|
const xVisible = setScrollbarVisibility(_overflowStyle.x, true);
|
||||||
const yVisible = setScrollbarVisibility(_overflowStyle.y, addRemoveClassVertical);
|
const yVisible = setScrollbarVisibility(_overflowStyle.y, false);
|
||||||
const hasCorner = xVisible && yVisible;
|
const hasCorner = xVisible && yVisible;
|
||||||
|
|
||||||
addRemoveClassHorizontal(classNamesScrollbarCornerless, !hasCorner);
|
scrollbarsAddRemoveClass(classNamesScrollbarCornerless, !hasCorner);
|
||||||
addRemoveClassVertical(classNamesScrollbarCornerless, !hasCorner);
|
|
||||||
}
|
}
|
||||||
if (themeChanged) {
|
if (themeChanged) {
|
||||||
addRemoveClassHorizontal(prevTheme);
|
scrollbarsAddRemoveClass(prevTheme);
|
||||||
addRemoveClassVertical(prevTheme);
|
scrollbarsAddRemoveClass(theme, true);
|
||||||
|
|
||||||
addRemoveClassHorizontal(theme, true);
|
|
||||||
addRemoveClassVertical(theme, true);
|
|
||||||
prevTheme = theme;
|
prevTheme = theme;
|
||||||
}
|
}
|
||||||
if (autoHideChanged) {
|
if (autoHideChanged) {
|
||||||
@@ -280,10 +266,10 @@ export const createScrollbarsSetup = (
|
|||||||
refreshScrollbarHandleOffset(
|
refreshScrollbarHandleOffset(
|
||||||
styleHorizontal,
|
styleHorizontal,
|
||||||
currStructureSetupState,
|
currStructureSetupState,
|
||||||
scrollOffsetElement,
|
_scrollOffsetElement,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
refreshScrollbarHandleOffset(styleVertical, currStructureSetupState, scrollOffsetElement);
|
refreshScrollbarHandleOffset(styleVertical, currStructureSetupState, _scrollOffsetElement);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollbarsSetupState,
|
scrollbarsSetupState,
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ export interface StructureSetupElementsObj {
|
|||||||
_padding: HTMLElement | false;
|
_padding: HTMLElement | false;
|
||||||
_content: HTMLElement | false;
|
_content: HTMLElement | false;
|
||||||
_viewportArrange: HTMLStyleElement | false | null | undefined;
|
_viewportArrange: HTMLStyleElement | false | null | undefined;
|
||||||
|
_scrollOffsetElement: HTMLElement;
|
||||||
|
_scrollEventElement: HTMLElement | Document;
|
||||||
// ctx ----
|
// ctx ----
|
||||||
_isTextarea: boolean;
|
_isTextarea: boolean;
|
||||||
_isBody: boolean;
|
_isBody: boolean;
|
||||||
@@ -152,6 +154,8 @@ export const createStructureSetupElements = (
|
|||||||
!_nativeScrollbarsHiding &&
|
!_nativeScrollbarsHiding &&
|
||||||
createUniqueViewportArrangeElement &&
|
createUniqueViewportArrangeElement &&
|
||||||
createUniqueViewportArrangeElement(env),
|
createUniqueViewportArrangeElement(env),
|
||||||
|
_scrollOffsetElement: isBody ? ownerDocument.documentElement : viewportElement,
|
||||||
|
_scrollEventElement: isBody ? ownerDocument : viewportElement,
|
||||||
_windowElm: wnd,
|
_windowElm: wnd,
|
||||||
_documentElm: ownerDocument,
|
_documentElm: ownerDocument,
|
||||||
_isTextarea: isTextarea,
|
_isTextarea: isTextarea,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ body > .os-scrollbar {
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
.os-scrollbar-handle {
|
.os-scrollbar-handle {
|
||||||
pointer-events: none;
|
pointer-events: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -33,6 +33,7 @@ body > .os-scrollbar {
|
|||||||
.os-scrollbar-handle-interactive,
|
.os-scrollbar-handle-interactive,
|
||||||
.os-scrollbar-track-interactive {
|
.os-scrollbar-track-interactive {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
touch-action: none;
|
||||||
}
|
}
|
||||||
.os-scrollbar-unusable,
|
.os-scrollbar-unusable,
|
||||||
.os-scrollbar-unusable * {
|
.os-scrollbar-unusable * {
|
||||||
|
|||||||
Reference in New Issue
Block a user