diff --git a/.changeset/breezy-pumas-rest.md b/.changeset/breezy-pumas-rest.md new file mode 100644 index 000000000..34c44bda2 --- /dev/null +++ b/.changeset/breezy-pumas-rest.md @@ -0,0 +1,6 @@ +--- +"@hi-ui/scrollbar": patch +"@hi-ui/hiui": patch +--- + +feat(scrollbar): 滚动条固定在屏幕底部 (#3019) diff --git a/packages/ui/scrollbar/src/Scrollbar.tsx b/packages/ui/scrollbar/src/Scrollbar.tsx index 432b735af..f72f2da71 100644 --- a/packages/ui/scrollbar/src/Scrollbar.tsx +++ b/packages/ui/scrollbar/src/Scrollbar.tsx @@ -9,6 +9,7 @@ import { ScrollbarEventProps, ScrollbarPositionEnum, ScrollbarHelpers, + Settings, } from './types' import { ScrollbarEventToPsMap } from './utils' @@ -28,6 +29,7 @@ export const Scrollbar = forwardRef( style, zIndex, innerRef, + settings = {}, ...rest }, ref @@ -43,7 +45,7 @@ export const Scrollbar = forwardRef( useEffect(() => { if (containerElement) { - setPs(new PerfectScrollbar(containerElement)) + setPs(new PerfectScrollbar(containerElement, settings)) // 动态设置滚动条 z-index zIndex && containerElement.style.setProperty('--scrollbar-zIndex', String(zIndex)) @@ -89,6 +91,9 @@ export const Scrollbar = forwardRef( () => ({ instance: ps, containerElement: containerElement || undefined, + updata: () => { + ps?.update() + }, }), [ps, containerElement] ) @@ -172,6 +177,11 @@ export interface ScrollbarProps extends HiBaseHTMLProps<'div'>, ScrollbarEventPr * @default false */ onlyScrollVisible?: boolean + /** + * 滚动条配置 + * @default {} + */ + settings: Settings } if (__DEV__) { diff --git a/packages/ui/scrollbar/src/types.ts b/packages/ui/scrollbar/src/types.ts index 7d4a1df08..5745ad78a 100644 --- a/packages/ui/scrollbar/src/types.ts +++ b/packages/ui/scrollbar/src/types.ts @@ -11,6 +11,10 @@ export interface ScrollbarHelpers { * 容器dom实例 */ container?: HTMLDivElement + /** + * 更新滚动条 + */ + updata?: () => void } export type ScrollbarAxesEnum = 'both' | 'x' | 'y' | 'none' @@ -69,3 +73,17 @@ export type ScrollbarPositionEnum = | 'fixed' | 'relative' | 'sticky' + +/** + * 更多配置请参考:https://github.com/mdbootstrap/perfect-scrollbar/blob/main/types/perfect-scrollbar.d.ts + */ +export type Settings = PerfectScrollbar.Options & { + /** + * 开启滚动条吸底 + */ + isBottomToScreenBottom?: boolean + /** + * 滚动条吸底距离 + */ + heightFromBottom?: number +} diff --git a/packages/ui/scrollbar/stories/fixed.stories.tsx b/packages/ui/scrollbar/stories/fixed.stories.tsx new file mode 100644 index 000000000..b25dc9e39 --- /dev/null +++ b/packages/ui/scrollbar/stories/fixed.stories.tsx @@ -0,0 +1,63 @@ +import React, { useRef, useEffect } from 'react' +import Scrollbar from '../src' + +/** + * @title 滚动条固定到屏幕底部 + * @desc 默认不开启 + */ +export const Fixed = () => { + const innerRef = useRef() + useEffect(() => { + document.addEventListener('scroll', () => { + innerRef.current?.updata() + }) + }, []) + return ( +
+ +
+
+
+
+
+
+ +
+ ) +} diff --git a/packages/ui/scrollbar/stories/index.stories.tsx b/packages/ui/scrollbar/stories/index.stories.tsx index 3df616e48..013ee1024 100644 --- a/packages/ui/scrollbar/stories/index.stories.tsx +++ b/packages/ui/scrollbar/stories/index.stories.tsx @@ -5,6 +5,7 @@ export * from './basic.stories' export * from './axes.stories' export * from './config.stories' export * from './event.stories' +export * from './fixed.stories' export default { title: 'Others/Scrollbar', diff --git a/packages/ui/table/stories/scrollbar.stories.tsx b/packages/ui/table/stories/scrollbar.stories.tsx index 66abfe6e3..92ff60d52 100644 --- a/packages/ui/table/stories/scrollbar.stories.tsx +++ b/packages/ui/table/stories/scrollbar.stories.tsx @@ -354,7 +354,11 @@ export const Scrollbar = () => { columns={column} data={data} maxHeight={300} - scrollbar={{ keepVisible: true, zIndex: 9 }} + scrollbar={{ + keepVisible: true, + zIndex: 9, + settings: { isBottomToScreenBottom: true, heightFromBottom: 20 }, + }} />
diff --git a/patches/perfect-scrollbar+1.5.5.patch b/patches/perfect-scrollbar+1.5.5.patch new file mode 100644 index 000000000..cb31b1db7 --- /dev/null +++ b/patches/perfect-scrollbar+1.5.5.patch @@ -0,0 +1,53 @@ +diff --git a/node_modules/perfect-scrollbar/dist/perfect-scrollbar.esm.js b/node_modules/perfect-scrollbar/dist/perfect-scrollbar.esm.js +index 4215b17..119e0da 100644 +--- a/node_modules/perfect-scrollbar/dist/perfect-scrollbar.esm.js ++++ b/node_modules/perfect-scrollbar/dist/perfect-scrollbar.esm.js +@@ -418,6 +418,16 @@ function getThumbSize(i, thumbSize) { + return thumbSize; + } + ++function getElementBottomToScreenBottomDistance(element, i) { ++ var rect = element.getBoundingClientRect(); ++ var elementTop = rect.top; ++ var elementHeight = element.offsetHeight || element.clientHeight; ++ var viewportHeight = window.innerHeight || document.documentElement.clientHeight; ++ viewportHeight -= (i.settings && i.settings.heightFromBottom) || 0; ++ var distance = viewportHeight - (elementTop + elementHeight); ++ return distance; ++} ++ + function updateCss(element, i) { + var xRailOffset = { width: i.railXWidth }; + var roundedScrollTop = Math.floor(element.scrollTop); +@@ -431,10 +441,23 @@ function updateCss(element, i) { + } else { + xRailOffset.left = element.scrollLeft; + } ++ var bottomToScreenBottomDistance = 0 ++ if(i.settings.isBottomToScreenBottom){ ++ bottomToScreenBottomDistance = getElementBottomToScreenBottomDistance( ++ element, ++ i ++ ); ++ } + if (i.isScrollbarXUsingBottom) { + xRailOffset.bottom = i.scrollbarXBottom - roundedScrollTop; ++ if (bottomToScreenBottomDistance < 0) { ++ xRailOffset.bottom -= bottomToScreenBottomDistance; ++ } + } else { + xRailOffset.top = i.scrollbarXTop + roundedScrollTop; ++ if (bottomToScreenBottomDistance < 0) { ++ xRailOffset.top += bottomToScreenBottomDistance; ++ } + } + set(i.scrollbarXRail, xRailOffset); + +@@ -1263,6 +1286,7 @@ var PerfectScrollbar = function PerfectScrollbar(element, userSettings) { + this.lastScrollTop = Math.floor(element.scrollTop); // for onScroll only + this.lastScrollLeft = element.scrollLeft; // for onScroll only + this.event.bind(this.element, 'scroll', function (e) { return this$1.onScroll(e); }); ++ this.event.bind(this.element, "mouseenter", function (e) { return this$1.update(e); }); + updateGeometry(this); + }; +