fix floating point scrollSize browser rounding

This commit is contained in:
Rene Haas
2021-03-30 12:21:26 +02:00
parent 67799989f0
commit 598454b66e
3 changed files with 68 additions and 18 deletions
@@ -12,7 +12,8 @@ import {
removeElements,
windowSize,
runEach,
equalWH,
equalBCRWH,
getBoundingClientRect,
} from 'support';
import {
classNameEnvironment,
@@ -87,14 +88,14 @@ const getRtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): {
const getFlexboxGlue = (parentElm: HTMLElement, childElm: HTMLElement): boolean => {
addClass(parentElm, classNameEnvironmentFlexboxGlue);
const minOffsetsizeParent = offsetSize(parentElm);
const minOffsetsize = offsetSize(childElm);
const supportsMin = equalWH(minOffsetsize, minOffsetsizeParent);
const minOffsetsizeParent = getBoundingClientRect(parentElm);
const minOffsetsize = getBoundingClientRect(childElm);
const supportsMin = equalBCRWH(minOffsetsize, minOffsetsizeParent, true);
addClass(parentElm, classNameEnvironmentFlexboxGlueMax);
const maxOffsetsizeParent = offsetSize(parentElm);
const maxOffsetsize = offsetSize(childElm);
const supportsMax = equalWH(maxOffsetsize, maxOffsetsizeParent);
const maxOffsetsizeParent = getBoundingClientRect(parentElm);
const maxOffsetsize = getBoundingClientRect(childElm);
const supportsMax = equalBCRWH(maxOffsetsize, maxOffsetsizeParent, true);
return supportsMin && supportsMax;
};
@@ -12,6 +12,8 @@ import {
addClass,
removeClass,
clientSize,
offsetSize,
getBoundingClientRect,
} from 'support';
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
import { getEnvironment } from 'environment';
@@ -21,6 +23,10 @@ import { classNameViewportScrollbarStyling } from 'classnames';
const overlaidScrollbarsHideOffset = 42;
const overlaidScrollbarsHideBorderStyle = `${overlaidScrollbarsHideOffset}px solid transparent`;
interface ContentScrollSizeCacheContext {
_viewportSize: WH<number>;
_viewportScrollSize: WH<number>;
}
interface OverflowAmountCacheContext {
_contentScrollSize: WH<number>;
_viewportSize: WH<number>;
@@ -29,26 +35,48 @@ interface OverflowAmountCacheContext {
export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
const { _structureSetup, _getPaddingStyle } = lifecycleHub;
const { _host, _padding, _viewport, _content, _contentArrange } = _structureSetup._targetObj;
const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache<WH<number>>(
() => scrollSize(_content || _viewport),
const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache<
WH<number>,
ContentScrollSizeCacheContext
>(
(ctx) => {
const { _viewportSize, _viewportScrollSize } = ctx;
const contentViewportScrollSize = scrollSize(_content || _viewport);
return _content ? fixScrollSizeRounding(contentViewportScrollSize, _viewportSize, _viewportScrollSize) : contentViewportScrollSize;
},
{ _equal: equalWH }
);
const { _update: updateOverflowAmountCache, _current: getCurrentOverflowAmountCache } = createCache<XY<number>, OverflowAmountCacheContext>(
(ctx) => ({
x: Math.max(0, Math.round((ctx._contentScrollSize.w - ctx._viewportSize.w) * 100) / 100),
y: Math.max(0, Math.round((ctx._contentScrollSize.h - ctx._viewportSize.h) * 100) / 100),
x: Math.max(0, ctx._contentScrollSize.w - ctx._viewportSize.w),
y: Math.max(0, ctx._contentScrollSize.h - ctx._viewportSize.h),
}),
{ _equal: equalXY }
);
const fixScrollSizeRounding = (contentScrollSize: WH<number>, viewportSize: WH<number>, viewportScrollSize: WH<number>): WH<number> => {
const equalViewportSizes = viewportSize.w === viewportScrollSize.w || viewportSize.h === viewportScrollSize.h;
const contentViewportScrollSizeOverflow = contentScrollSize.w > viewportScrollSize.w || contentScrollSize.h > viewportScrollSize.h;
if (equalViewportSizes && contentViewportScrollSizeOverflow) {
const viewportRect = getBoundingClientRect(_viewport);
const viewportOffsetSize = offsetSize(_viewport);
return {
w: contentScrollSize.w - Math.ceil(Math.max(0, viewportRect.width - viewportOffsetSize.w)),
h: contentScrollSize.h - Math.ceil(Math.max(0, viewportRect.height - viewportOffsetSize.h)),
};
}
return contentScrollSize;
};
const setViewportOverflowStyle = (horizontal: boolean, amount: number, behavior: OverflowBehavior, styleObj: StyleObject) => {
const overflowKey = horizontal ? 'overflowX' : 'overflowY';
//const scrollMaxKey = horizontal ? 'scrollLeftMax' : 'scrollTopMax';
const behaviorIsScroll = behavior === 'scroll';
const behaviorIsVisibleScroll = behavior === 'visible-scroll';
const hideOverflow = behaviorIsScroll || behavior === 'hidden';
//const scrollMax = _viewport[scrollMaxKey];
//const scrollMaxOverflow = isNumber(scrollMax) ? scrollMax > 0 : true;
const applyStyle = amount > 0 && hideOverflow;
if (applyStyle) {
@@ -156,7 +184,11 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
const contentClientSize = clientSize(_content || _viewport); // needs to be client Size because applied border for content arrange on content
const contentArrangeOffsetSize = clientSize(_contentArrange); // can be offset size aswell
contentScrollSizeCache = updateContentScrollSizeCache(force);
contentScrollSizeCache = updateContentScrollSizeCache(force, {
_viewportSize: viewportSize,
_viewportScrollSize: viewportScrollSize,
});
const { _value: contentScrollSize } = contentScrollSizeCache;
overflowAmuntCache = updateOverflowAmountCache(force, {
_contentScrollSize: {
@@ -230,8 +262,8 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
// TODO: Test without content
// TODO: Test without padding
// TODO: hide host || padding overflow if scroll x or y
// TODO: fix false overflow bug (fractal scroll size)
// TODO: add trinsic lifecycle
// TODO: IE max-width fix not always working
// TODO: remove lifecycleHub get set padding if not needed
style(_viewport, viewportStyle);
@@ -8,11 +8,18 @@ import { PlainObject } from 'typings';
* @param b Object b.
* @param props The props which shall be compared.
*/
export const equal = <T extends PlainObject>(a: T | undefined, b: T | undefined, props: Array<keyof T>): boolean => {
export const equal = <T extends PlainObject>(
a: T | undefined,
b: T | undefined,
props: Array<keyof T>,
propMutation?: ((value: any) => any) | null | false
): boolean => {
if (a && b) {
let result = true;
each(props, (prop) => {
if (a[prop] !== b[prop]) {
const compareA = propMutation ? propMutation(a[prop]) : a[prop];
const compareB = propMutation ? propMutation(b[prop]) : b[prop];
if (compareA !== compareB) {
result = false;
}
});
@@ -44,3 +51,13 @@ export const equalXY = (a?: XY, b?: XY) => equal<XY>(a, b, ['x', 'y']);
* @param b Object b.
*/
export const equalTRBL = (a?: TRBL, b?: TRBL) => equal<TRBL>(a, b, ['t', 'r', 'b', 'l']);
/**
* Compares two DOM Rects for their equality of their width and height properties
* Also returns false if one of the DOM Rects is undefined or null.
* @param a DOM Rect a.
* @param b DOM Rect b.
* @param round Whether the values should be rounded.
*/
export const equalBCRWH = (a?: DOMRect, b?: DOMRect, round?: boolean) =>
equal<DOMRect>(a, b, ['width', 'height'], round && ((value) => Math.round(value)));