Finalize animated masked scroll behavior
This commit is contained in:
parent
60c5314c3a
commit
1cdeea0346
@ -164,7 +164,7 @@ export default function CommandKClient({
|
||||
}, [mobileViewportHeight]);
|
||||
|
||||
const refScroll = useRef<HTMLDivElement>(null);
|
||||
const { maskImage, updateMask } = useMaskedScroll({
|
||||
const { maskStyle, updateMask } = useMaskedScroll({
|
||||
ref: refScroll,
|
||||
updateMaskOnEvents: false,
|
||||
});
|
||||
@ -591,7 +591,7 @@ export default function CommandKClient({
|
||||
'mx-3 pt-2 pb-3.5',
|
||||
'[&>*>*>*]:mt-2.5',
|
||||
)}
|
||||
style={{ maskImage, maxHeight }}
|
||||
style={{ ...maskStyle, maxHeight }}
|
||||
>
|
||||
<div className="-mt-2.5">
|
||||
<Command.Empty className="mt-1 pl-3 text-dim pb-1">
|
||||
|
||||
@ -17,7 +17,7 @@ MaskedScrollExternalProps & {
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { maskImage } = useMaskedScroll({
|
||||
const { maskStyle } = useMaskedScroll({
|
||||
ref,
|
||||
direction,
|
||||
fadeSize,
|
||||
@ -34,7 +34,7 @@ MaskedScrollExternalProps & {
|
||||
hideScrollbar && '[scrollbar-width:none]',
|
||||
className,
|
||||
)}
|
||||
style={{ maskImage, ...style }}
|
||||
style={{ ...maskStyle, ...style }}
|
||||
>
|
||||
{children}
|
||||
</div>;
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
import { RefObject, useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
CSSProperties,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
const CSS_VAR_START = '--mask-start';
|
||||
const CSS_VAR_END = '--mask-end';
|
||||
|
||||
export interface MaskedScrollExternalProps {
|
||||
direction?: 'vertical' | 'horizontal'
|
||||
fadeSize?: number
|
||||
animationDuration?: number
|
||||
scrollToEndOnMount?: boolean
|
||||
}
|
||||
|
||||
@ -13,6 +20,7 @@ export default function useMaskedScroll({
|
||||
ref: containerRef,
|
||||
direction = 'vertical',
|
||||
fadeSize = 24,
|
||||
animationDuration = 0.3,
|
||||
// Disable when calling 'updateMask' explicitly
|
||||
updateMaskOnEvents = true,
|
||||
scrollToEndOnMount,
|
||||
@ -26,11 +34,11 @@ export default function useMaskedScroll({
|
||||
const ref = containerRef?.current;
|
||||
if (ref) {
|
||||
const start = isVertical
|
||||
? ref.scrollTop === 0
|
||||
: ref.scrollLeft === 0;
|
||||
? ref.scrollTop < 1
|
||||
: ref.scrollLeft < 1;
|
||||
const end = isVertical
|
||||
? Math.abs((ref.scrollHeight - ref.scrollTop) - ref.clientHeight) < 1
|
||||
: Math.abs((ref.scrollWidth - ref.scrollLeft) - ref.clientWidth) < 1;
|
||||
? (ref.scrollHeight - ref.scrollTop) - ref.clientHeight < 1
|
||||
: (ref.scrollWidth - ref.scrollLeft) - ref.clientWidth < 1;
|
||||
ref.style.setProperty(CSS_VAR_START, `${!start ? fadeSize : 0}px`);
|
||||
ref.style.setProperty(CSS_VAR_END, `${!end ? fadeSize : 0}px`);
|
||||
}
|
||||
@ -61,13 +69,40 @@ export default function useMaskedScroll({
|
||||
}
|
||||
}, [containerRef, scrollToEndOnMount, isVertical]);
|
||||
|
||||
const maskImage = useMemo(() => {
|
||||
let mask = `linear-gradient(to ${isVertical ? 'bottom' : 'right'}, `;
|
||||
mask += 'transparent, black ';
|
||||
mask += `var(${CSS_VAR_START}), black calc(100% - `;
|
||||
mask += `var(${CSS_VAR_END})), transparent)`;
|
||||
return mask;
|
||||
}, [isVertical]);
|
||||
useEffect(() => {
|
||||
try {
|
||||
window.CSS.registerProperty({
|
||||
name: CSS_VAR_START,
|
||||
syntax: '<length>',
|
||||
initialValue: '0px',
|
||||
inherits: false,
|
||||
});
|
||||
window.CSS.registerProperty({
|
||||
name: CSS_VAR_END,
|
||||
syntax: '<length>',
|
||||
initialValue: '0px',
|
||||
inherits: false,
|
||||
});
|
||||
} catch {}
|
||||
}, []);
|
||||
|
||||
return { maskImage, updateMask };
|
||||
const maskStyle: CSSProperties = useMemo(() => {
|
||||
// eslint-disable-next-line max-len
|
||||
const gradientStart = `linear-gradient(to ${isVertical ? 'bottom' : 'right'}, transparent, black var(${CSS_VAR_START}))`;
|
||||
// eslint-disable-next-line max-len
|
||||
const gradientEnd = `linear-gradient(to ${isVertical ? 'top' : 'left'}, transparent, black var(${CSS_VAR_END}))`;
|
||||
const maskImage = [gradientStart, gradientEnd].join(', ');
|
||||
const transition = [
|
||||
`${CSS_VAR_START} ${animationDuration}s ease-in-out`,
|
||||
`${CSS_VAR_END} ${animationDuration}s ease-in-out`,
|
||||
].join(', ');
|
||||
return {
|
||||
maskImage,
|
||||
maskComposite: 'intersect',
|
||||
maskRepeat: 'no-repeat',
|
||||
transition,
|
||||
};
|
||||
}, [isVertical, animationDuration]);
|
||||
|
||||
return { maskStyle, updateMask };
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user