Skip to content

Commit

Permalink
improve update strategy and debounce
Browse files Browse the repository at this point in the history
  • Loading branch information
KingSora committed Oct 15, 2023
1 parent 48e8892 commit b44161c
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 87 deletions.
1 change: 1 addition & 0 deletions local/browser-testing/src/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export const resize = (element: HTMLElement) => {
});

return {
resizeBtn,
addResizeListener(listener: ResizeListener) {
resizeListeners.push(listener);
},
Expand Down
38 changes: 11 additions & 27 deletions packages/overlayscrollbars/src/observers/sizeObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
scrollElementTo,
selfClearTimeout,
wnd,
domRectAppeared,
} from '~/support';
import { getEnvironment } from '~/environment';
import {
Expand Down Expand Up @@ -73,12 +74,6 @@ export const createSizeObserver = (
const [updateResizeObserverContentRectCache] = createCache<DOMRectReadOnly | false>({
_initialValue: false,
_alwaysUpdateValues: true,
_equal: (currVal, newVal) =>
!(
!currVal || // if no initial value
// if from display: none to display: block
(!domRectHasDimensions(currVal) && domRectHasDimensions(newVal))
),
});

return () => {
Expand Down Expand Up @@ -112,10 +107,10 @@ export const createSizeObserver = (
sizeChangedContext.contentRect
);
const hasDimensions = domRectHasDimensions(currRContentRect);
const hadDimensions = domRectHasDimensions(prevContentRect);
const appeared = domRectAppeared(currRContentRect, prevContentRect);
const firstCall = !prevContentRect;
appear = !firstCall && !hadDimensions && hasDimensions;
skip = !appear && ((firstCall && !!hadDimensions) || !hasDimensions || isWindowResize); // skip on initial RO. call (if the element visible) or if display is none or when window resize
appear = firstCall || appeared;
skip = !appear && (!hasDimensions || isWindowResize); // skip if display is none or when window resize

doDirectionScroll = !skip; // direction scroll when not skipping
}
Expand All @@ -138,16 +133,14 @@ export const createSizeObserver = (

if (!skip) {
onSizeChangedCallback({
_sizeChanged: !hasDirectionCache,
_directionIsRTLCache: hasDirectionCache ? sizeChangedContext : undefined,
_sizeChanged: !hasDirectionCache,
_appear: appear,
});
}

isWindowResize = false;
};
let appearCallback: typeof onSizeChangedCallbackProxy | undefined | false =
observeAppearChange && onSizeChangedCallbackProxy;

if (ResizeObserverConstructor) {
const resizeObserverInstance = new ResizeObserverConstructor((entries) =>
Expand All @@ -158,13 +151,16 @@ export const createSizeObserver = (
resizeObserverInstance.disconnect();
});
} else if (sizeObserverPlugin) {
const [pluginAppearCallback, pluginOffListeners] = sizeObserverPlugin(
const [pluginAppearCallback, pluginDestroyFns] = sizeObserverPlugin(
listenerElement,
onSizeChangedCallbackProxy,
observeAppearChange
);
appearCallback = pluginAppearCallback;
push(destroyFns, pluginOffListeners);
push(destroyFns, [
pluginDestroyFns,
addClass(sizeObserver, classNameSizeObserverAppear),
addEventListener(sizeObserver, 'animationstart', pluginAppearCallback),
]);
} else {
return noop;
}
Expand Down Expand Up @@ -199,18 +195,6 @@ export const createSizeObserver = (
);
}

// appearCallback is always needed on scroll-observer strategy to reset it
if (appearCallback) {
addClass(sizeObserver, classNameSizeObserverAppear);
push(
destroyFns,
addEventListener(sizeObserver, 'animationstart', bind(appearCallback, true), {
// Fire only once for "CSS is ready" event if ResizeObserver strategy is used
_once: !!ResizeObserverConstructor,
})
);
}

return bind(runEachAndClear, push(destroyFns, appendChildren(target, sizeObserver)));
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
createCache,
debounce,
getDirectionIsRTL,
domRectHasDimensions,
each,
equalWH,
fractionalSize,
Expand All @@ -21,6 +20,7 @@ import {
getElmentScroll,
scrollElementTo,
inArray,
domRectAppeared,
} from '~/support';
import { createDOMObserver, createSizeObserver, createTrinsicObserver } from '~/observers';
import { getEnvironment } from '~/environment';
Expand Down Expand Up @@ -71,6 +71,9 @@ export const createObserversSetup = (
let debounceMaxDelay: number | false | undefined;
let updateContentMutationObserver: (() => void) | undefined;
let destroyContentMutationObserver: (() => void) | undefined;
let prevContentRect: DOMRectReadOnly | undefined;

const { _nativeScrollbarsHiding } = getEnvironment();

const hostSelector = `[${dataAttributeHost}]`;

Expand Down Expand Up @@ -186,12 +189,24 @@ export const createObserversSetup = (
_directionIsRTLCache,
_appear,
}: SizeObserverCallbackParams) => {
const updateFn = !_sizeChanged || _appear ? onObserversUpdated : onObserversUpdatedDebounced;
const exclusiveSizeChange = _sizeChanged && !_appear && !_directionIsRTLCache;
const updateFn =
// use debounceed update:
// if native scrollbars hiding is supported
// and if the update is more than just a exclusive sizeChange (e.g. size change + appear, or size change + direction)
!exclusiveSizeChange && _nativeScrollbarsHiding
? onObserversUpdatedDebounced
: onObserversUpdated;

const [directionIsRTL, directionIsRTLChanged] = _directionIsRTLCache || [];

_directionIsRTLCache && assignDeep(state, { _directionIsRTL: directionIsRTL });

updateFn({ _sizeChanged, _appear, _directionChanged: directionIsRTLChanged });
updateFn({
_sizeChanged: _sizeChanged || _appear,
_appear,
_directionChanged: directionIsRTLChanged,
});
};

const onContentMutation = (
Expand All @@ -202,6 +217,7 @@ export const createObserversSetup = (
const updateHints = {
_contentMutation: contentSizeChanged,
};

// if contentChangedThroughEvent is true its already debounced
const updateFn = contentChangedThroughEvent ? onObserversUpdated : onObserversUpdatedDebounced;

Expand Down Expand Up @@ -247,18 +263,16 @@ export const createObserversSetup = (
}
);

let prevContentRect: DOMRectReadOnly | undefined;
const viewportIsTargetResizeObserver =
_viewportIsTarget &&
ResizeObserverConstructor &&
new ResizeObserverConstructor((entries) => {
const currRContentRect = entries[entries.length - 1].contentRect;
const hasDimensions = domRectHasDimensions(currRContentRect);
const hadDimensions = domRectHasDimensions(prevContentRect);
const _appear = !hadDimensions && hasDimensions;

onSizeChanged({ _sizeChanged: true, _appear });
prevContentRect = currRContentRect;
const currContentRect = entries[entries.length - 1].contentRect;
onSizeChanged({
_sizeChanged: true,
_appear: domRectAppeared(currContentRect, prevContentRect),
});
prevContentRect = currContentRect;
});

return [
Expand All @@ -278,14 +292,15 @@ export const createObserversSetup = (
destroyHostMutationObserver();
};
},
({ _checkOption, _takeRecords }) => {
({ _checkOption, _takeRecords, _force }) => {
const updateHints: ObserversSetupUpdateHints = {};

const [ignoreMutation] = _checkOption('update.ignoreMutation');
const [attributes, attributesChanged] = _checkOption('update.attributes');
const [elementEvents, elementEventsChanged] = _checkOption('update.elementEvents');
const [debounceValue, debounceChanged] = _checkOption('update.debounce');
const contentMutationObserverChanged = elementEventsChanged || attributesChanged;
const takeRecords = _takeRecords || _force;
const ignoreMutationFromOptions = (mutation: MutationRecord) =>
isFunction(ignoreMutation) && ignoreMutation(mutation);

Expand Down Expand Up @@ -336,7 +351,7 @@ export const createObserversSetup = (
}
}

if (_takeRecords) {
if (takeRecords) {
const hostUpdateResult = updateHostMutationObserver();
const trinsicUpdateResult = updateTrinsicObserver && updateTrinsicObserver();
const contentUpdateResult =
Expand All @@ -345,14 +360,14 @@ export const createObserversSetup = (
hostUpdateResult &&
assignDeep(
updateHints,
onHostMutation(hostUpdateResult[0], hostUpdateResult[1], _takeRecords)
onHostMutation(hostUpdateResult[0], hostUpdateResult[1], takeRecords)
);

trinsicUpdateResult &&
assignDeep(updateHints, onTrinsicChanged(trinsicUpdateResult[0], _takeRecords));
assignDeep(updateHints, onTrinsicChanged(trinsicUpdateResult[0], takeRecords));

contentUpdateResult &&
assignDeep(updateHints, onContentMutation(contentUpdateResult[0], _takeRecords));
assignDeep(updateHints, onContentMutation(contentUpdateResult[0], takeRecords));
}

return updateHints;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ export const createScrollbarsSetup = (
const { _overflowEdgeChanged, _overflowAmountChanged, _overflowStyleChanged } =
_structureUpdateHints || {};
const { _directionChanged, _appear } = _observersUpdateHints || {};
const { _nativeScrollbarsOverlaid } = getEnvironment();
const { _directionIsRTL } = observersSetupState;
const { _nativeScrollbarsOverlaid } = getEnvironment();
const { _overflowAmount, _overflowStyle, _hasOverflow } = structureSetupState;
const [showNativeOverlaidScrollbarsOption, showNativeOverlaidScrollbarsChanged] =
_checkOption('showNativeOverlaidScrollbars');
Expand All @@ -169,7 +169,8 @@ export const createScrollbarsSetup = (
const [clickScroll, clickScrollChanged] = _checkOption('scrollbars.clickScroll');
const trulyAppeared = _appear && !_force;
const hasOverflow = _hasOverflow.x || _hasOverflow.y;
const updateScrollbars = _overflowEdgeChanged || _overflowAmountChanged || _directionChanged;
const updateScrollbars =
_overflowEdgeChanged || _overflowAmountChanged || _directionChanged || _force;
const updateVisibility = _overflowStyleChanged || visibilityChanged;
const showNativeOverlaidScrollbars =
showNativeOverlaidScrollbarsOption &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,12 @@ export const createOverflowUpdateSegment: CreateStructureUpdateSegment = (
_hostMutation ||
showNativeOverlaidScrollbarsChanged ||
_heightIntrinsicChanged);
const adjustViewportArrange =
_sizeChanged ||
_paddingStyleChanged ||
_contentMutation ||
_directionChanged ||
showNativeOverlaidScrollbarsChanged;
const overflowXVisible = overflowIsVisible(overflow.x);
const overflowYVisible = overflowIsVisible(overflow.y);
const overflowVisible = overflowXVisible || overflowYVisible;
Expand All @@ -378,13 +384,7 @@ export const createOverflowUpdateSegment: CreateStructureUpdateSegment = (
fixFlexboxGlue(preMeasureViewportOverflowState, _heightIntrinsic);
}

if (
_sizeChanged ||
_paddingStyleChanged ||
_contentMutation ||
_directionChanged ||
showNativeOverlaidScrollbarsChanged
) {
if (adjustViewportArrange) {
if (overflowVisible) {
_viewportAddRemoveClass(
dataValueViewportOverflowVisible,
Expand Down Expand Up @@ -463,8 +463,7 @@ export const createOverflowUpdateSegment: CreateStructureUpdateSegment = (
(overflowXVisible && overflowYVisible && (hasOverflow.x || hasOverflow.y)) ||
(overflowXVisible && hasOverflow.x && !hasOverflow.y) ||
(overflowYVisible && hasOverflow.y && !hasOverflow.x);

if (
const adjustVuewportStyle =
_paddingStyleChanged ||
_directionChanged ||
sizeFractionChanged ||
Expand All @@ -473,8 +472,10 @@ export const createOverflowUpdateSegment: CreateStructureUpdateSegment = (
overflowAmountChanged ||
overflowChanged ||
showNativeOverlaidScrollbarsChanged ||
adjustFlexboxGlue
) {
adjustFlexboxGlue ||
adjustViewportArrange;

if (adjustVuewportStyle) {
const viewportStyle: StyleObject = {
[strMarginRight]: 0,
[strMarginBottom]: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type { CreateStructureUpdateSegment } from '../structureSetup';
* @returns
*/
export const createPaddingUpdateSegment: CreateStructureUpdateSegment = (
{ _host, _padding, _viewport, _viewportIsTarget: _isSingleElm },
{ _host, _padding, _viewport, _viewportIsTarget },
state
) => {
const [updatePaddingCache, currentPaddingCache] = createCache(
Expand All @@ -37,22 +37,22 @@ export const createPaddingUpdateSegment: CreateStructureUpdateSegment = (

return ({ _checkOption, _observersUpdateHints, _observersState, _force }) => {
let [padding, paddingChanged] = currentPaddingCache(_force);
const { _nativeScrollbarsHiding: _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
const { _nativeScrollbarsHiding, _flexboxGlue } = getEnvironment();
const { _sizeChanged, _contentMutation, _directionChanged } = _observersUpdateHints || {};
const { _directionIsRTL } = _observersState;
const [paddingAbsolute, paddingAbsoluteChanged] = _checkOption('paddingAbsolute');
const contentMutation = !_flexboxGlue && _contentMutation;
const contentMutation = _force || (!_flexboxGlue && _contentMutation);

if (_sizeChanged || paddingChanged || contentMutation) {
[padding, paddingChanged] = updatePaddingCache(_force);
}

const paddingStyleChanged =
!_isSingleElm && (paddingAbsoluteChanged || _directionChanged || paddingChanged);
!_viewportIsTarget && (paddingAbsoluteChanged || _directionChanged || paddingChanged);

if (paddingStyleChanged) {
// if there is no padding element and no scrollbar styling, paddingAbsolute isn't supported
const paddingRelative = !paddingAbsolute || (!_padding && !_nativeScrollbarStyling);
const paddingRelative = !paddingAbsolute || (!_padding && !_nativeScrollbarsHiding);
const paddingHorizontal = padding.r + padding.l;
const paddingVertical = padding.t + padding.b;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import type { CreateStructureUpdateSegment } from '../structureSetup';
*/
export const createTrinsicUpdateSegment: CreateStructureUpdateSegment =
({ _content }) =>
({ _observersUpdateHints, _observersState }) => {
({ _observersUpdateHints, _observersState, _force }) => {
const { _flexboxGlue } = getEnvironment();
const { _heightIntrinsicChanged } = _observersUpdateHints || {};
const { _heightIntrinsic } = _observersState;
const heightIntrinsicChanged = (_content || !_flexboxGlue) && _heightIntrinsicChanged;
const heightIntrinsicChanged =
(_content || !_flexboxGlue) && (_heightIntrinsicChanged || _force);

if (heightIntrinsicChanged) {
style(_content, {
Expand Down
15 changes: 15 additions & 0 deletions packages/overlayscrollbars/src/support/dom/dimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,18 @@ export const hasDimensions = (elm: HTMLElement | false | null | undefined): bool
*/
export const domRectHasDimensions = (rect?: DOMRectReadOnly | false | null) =>
!!(rect && (rect[strHeight] || rect[strWidth]));

/**
* Determines whether current DOM Rect has appeared according the the previous dom rect..
* @param currContentRect The current DOM Rect.
* @param prevContentRect The previous DOM Rect.
* @returns Whether the dom rect appeared.
*/
export const domRectAppeared = (
currContentRect: DOMRectReadOnly | false | null | undefined,
prevContentRect: DOMRectReadOnly | false | null | undefined
) => {
const rectHasDimensions = domRectHasDimensions(currContentRect);
const rectHadDimensions = domRectHasDimensions(prevContentRect);
return !rectHadDimensions && rectHasDimensions;
};
Loading

0 comments on commit b44161c

Please sign in to comment.