@ohkit/back-top 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) [2025], [wuqiuyang]
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # @ohkit/back-top
2
+
3
+ 返回顶部组件。当页面或容器滚动超过指定距离后自动显示返回顶部按钮,点击后平滑滚动到顶部。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @ohkit/back-top
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ### 基本用法
14
+ ```tsx
15
+ import { BackTop } from '@ohkit/back-top';
16
+ import '@ohkit/back-top/dist/index.css';
17
+
18
+ function App() {
19
+ return (
20
+ <div style={{ height: '100vh', overflow: 'auto' }}>
21
+ <div style={{ minHeight: '2000px' }}>
22
+ {/* 很长的高度内容 */}
23
+ </div>
24
+ {/* 组件放入滚动容器中,自动查找所属的滚动容器 */}
25
+ <BackTop />
26
+ </div>
27
+ );
28
+ }
29
+ ```
30
+
31
+ ### 指定滚动容器
32
+ ```tsx
33
+ import { useRef } from 'react';
34
+ import { BackTop } from '@ohkit/back-top';
35
+
36
+ function CustomContainer() {
37
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
38
+
39
+ return (
40
+ <section>
41
+ <div ref={scrollContainerRef} style={{ height: '400px', overflow: 'auto' }}>
42
+ <div style={{ minHeight: '800px' }}>
43
+ {/* 内容 */}
44
+ </div>
45
+ </div>
46
+ <BackTop
47
+ scrollRefDom={scrollContainerRef}
48
+ mountType="absolute"
49
+ />
50
+ </section>
51
+ );
52
+ }
53
+ ```
54
+
55
+ ### 自定义按钮内容
56
+ ```tsx
57
+ <BackTop title="回到顶部" children={<>↑<br/>顶部</>} />
58
+ ```
59
+
60
+ ## API
61
+
62
+ ### BackTop Props
63
+
64
+ | 属性 | 类型 | 默认值 | 说明 |
65
+ |------|------|--------|------|
66
+ | className | string | '' | 自定义类名 |
67
+ | children | ReactNode | '↑' | 按钮内容,支持React节点 |
68
+ | mountType | 'fixed' \| 'absolute' | 'fixed' | 按钮定位方式 |
69
+ | scrollTop | number | 500 | 显示按钮的滚动阈值(像素) |
70
+ | scrollRefDom | React.RefObject<HTMLElement> | - | 自定义滚动容器 |
71
+ | position | 'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' | 'bottom-right' | 按钮位置 |
72
+ | offset | [number, number] | [50, 100] | 按钮偏移量 [top/bottom, left/right] |
73
+ | realScroll | boolean | false | 是否查找真实可滚动容器 |
74
+ | title | string | '返回顶部' | 按钮提示文本 |
75
+
76
+ ## 特性说明
77
+
78
+ ### 自动滚动容器检测
79
+ 组件会自动查找包含它的第一个滚动容器。如果设置 `realScroll={true}`,会查找 `scrollHeight > clientHeight` 的真实滚动容器。
80
+
81
+ ### 平滑滚动
82
+ 点击按钮时会使用 `scrollTo({ top: 0, behavior: 'smooth' })` 实现平滑滚动效果。
83
+
84
+ ### 性能优化
85
+ - 使用防抖机制避免频繁的滚动事件处理
86
+ - 只有滚动状态变化时才触发重渲染
87
+
88
+ ## 依赖说明
89
+
90
+ 组件依赖以下工具包:
91
+ - `@ohkit/dom-helper`:DOM事件处理
92
+ - `@ohkit/prefix-classname`:CSS类名前缀
93
+ - `@ohkit/react-helper`:React运行时工具
94
+
95
+ ## 样式定制
96
+
97
+ 组件支持通过 `className` 属性进行样式覆盖:
98
+
99
+ ```tsx
100
+ <BackTop className="custom-back-top" />
101
+ ```
102
+
103
+ 然后通过CSS或CSS-in-JS定义样式:
104
+
105
+ ```css
106
+ .custom-back-top {
107
+ background: #007bff;
108
+ border-radius: 8px;
109
+ /* 其他自定义样式 */
110
+ }
111
+ ```
package/dist/index.css ADDED
@@ -0,0 +1,2 @@
1
+ .ohkit-back-top__container{align-items:center;border-radius:4px;box-sizing:border-box;color:#fff;display:flex;font-size:14px;justify-content:center;line-height:1.2em;min-height:40px;min-width:40px;padding:4px;position:fixed;transition:all .2s ease-in;white-space:pre-line;z-index:10}.ohkit-back-top__absolute{bottom:20px;position:absolute;right:20px}.ohkit-back-top__invisible{background:transparent;opacity:0;pointer-events:none}.ohkit-back-top__visible{background:rgba(78,85,98,.25);cursor:pointer;opacity:1}.ohkit-back-top__visible:hover{background:rgba(78,85,98,.4)}
2
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["style.scss"],"names":[],"mappings":"AAAA,2BASE,kBAAmB,CAKnB,iBAAkB,CAZlB,qBAAsB,CAUtB,UAAW,CAJX,YAAa,CAGb,cAAe,CADf,sBAAuB,CAJvB,iBAAkB,CADlB,eAAgB,CADhB,cAAe,CADf,WAAY,CAFZ,cAAe,CAcf,0BAA4B,CAF5B,oBAAqB,CANrB,UASF,CACA,0BAGE,WAAY,CAFZ,iBAAkB,CAClB,UAEF,CACA,2BAGE,sBAAuB,CADvB,SAAU,CADV,mBAGF,CACA,yBACE,6BAAkC,CAElC,cAAe,CADf,SAEF,CACA,+BACE,4BACF","file":"index.css","sourcesContent":[".ohkit-back-top__container {\n position: fixed;\n box-sizing: border-box;\n padding: 4px;\n min-width: 40px;\n min-height: 40px;\n line-height: 1.2em;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: #fff;\n white-space: pre-line;\n border-radius: 4px;\n transition: all 0.2s ease-in;\n}\n.ohkit-back-top__absolute {\n position: absolute;\n right: 20px;\n bottom: 20px;\n}\n.ohkit-back-top__invisible {\n pointer-events: none;\n opacity: 0;\n background: transparent;\n}\n.ohkit-back-top__visible {\n background: rgba(78, 85, 98, 0.25);\n opacity: 1;\n cursor: pointer;\n}\n.ohkit-back-top__visible:hover {\n background: rgba(78, 85, 98, 0.4);\n}"]}
@@ -0,0 +1,2 @@
1
+ import o,{useState as r,useRef as e,useMemo as l,useEffect as i}from"react";import{prefixClassname as t,classNames as s}from"@ohkit/prefix-classname";import{useRuntime as n,useScrollEntry as c}from"@ohkit/react-helper";import{addEventListener as a}from"@ohkit/dom-helper";var m=t("ohkit-back-top__"),v=function(t){var v=t.children,p=t.className,u=t.scrollTop,f=void 0===u?500:u,d=t.mountType,b=void 0===d?"fixed":d,h=t.position,R=void 0===h?"bottom-right":h,k=t.offset,L=void 0===k?[50,100]:k,x=t.title,T=void 0===x?"返回顶部":x,y=t.scrollRefDom,V=t.realScroll,g=void 0!==V&&V,C=r(!1),E=C[0],N=C[1],S=e(null),_=n({preVisible:E,scrollListenerRemover:null})[0],D=c(y||S,{realScroll:g,autoFind:!0,intervalTime:3e3}),F=l(function(){var o,r=R.split("-"),e=r[1];return(o={})[r[0]]=L[0]+"px",o[e]=L[1]+"px",o},[R,L]);return i(function(){if(console.log("scrollEntry",D),D&&D.scroller){null==_.scrollListenerRemover||_.scrollListenerRemover();var o=function(){var o=D.scrollContainer,r=!!(o&&o.scrollTop>f);_.preVisible!==r&&(_.preVisible=r,N(r))};return o(),_.scrollListenerRemover=a(D.scroller,"scroll",o),function(){null==_.scrollListenerRemover||_.scrollListenerRemover()}}},[D.scroller]),/*#__PURE__*/o.createElement("div",{title:T,ref:S,style:F,className:s(m("container",{visible:!!E,invisible:!E}),m({absolute:"absolute"===b}),p),onClick:function(){var o;null==(o=D.scroller)||o.scrollTo({top:0,behavior:"smooth"})}},v||"↑")};export{v as BackTop,m as c};
2
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/index.tsx"],"sourcesContent":["import React, { PropsWithChildren, useEffect, useState, useMemo, useRef } from \"react\";\nimport {\n prefixClassname as p,\n classNames as cx,\n} from \"@ohkit/prefix-classname\";\nimport { useRuntime, useScrollEntry } from \"@ohkit/react-helper\";\nimport { addEventListener } from \"@ohkit/dom-helper\";\n\nimport \"./style.scss\";\n\nexport const c = p(\"ohkit-back-top__\");\n\nexport interface IBackTop {\n className?: string;\n /**\n * 按钮位置,fixed 或 absolute\n * @default \"fixed\"\n */\n mountType?: \"fixed\" | \"absolute\";\n /**\n * 滚动距离,当滚动距离大于此值时显示返回顶部按钮\n * @default 500\n */\n scrollTop?: number;\n scrollRefDom?: React.MutableRefObject<HTMLElement>;\n /**\n * 按钮位置,top-right 或 top-left 或 bottom-right 或 bottom-left\n * @default \"bottom-right\"\n */\n position?: \"top-right\" | \"top-left\" | \"bottom-right\" | \"bottom-left\";\n /**\n * 按钮位置偏移量px, 即[position.split(\"-\")[0], position.split(\"-\")[1]]\n * @default[50, 100]\n */\n offset?: [number, number];\n /**\n * 是否查找真实可滚动的容器,scrollHeight > clientHeight\n * @default false\n */\n realScroll?: boolean;\n /**\n * 按钮标题\n * @default \"返回顶部\"\n */\n title?: string;\n}\n\nexport type BackTopProps = PropsWithChildren<IBackTop>;\n\nexport const BackTop = ({\n children,\n className,\n scrollTop = 500,\n mountType = \"fixed\",\n position = \"bottom-right\",\n offset = [50, 100],\n title = \"返回顶部\",\n scrollRefDom,\n realScroll = false,\n}: BackTopProps) => {\n const [visible, setVisible] = useState(false);\n const domRef = useRef<HTMLDivElement>(null);\n const [runtime] = useRuntime<{\n preVisible: boolean;\n scrollListenerRemover: null | (() => void);\n }>({\n preVisible: visible,\n scrollListenerRemover: null,\n });\n\n const scrollEntry = useScrollEntry(scrollRefDom ? scrollRefDom : domRef, {\n realScroll,\n autoFind: true,\n intervalTime: 3000,\n });\n\n const positionStyle = useMemo(() => {\n const [x, y] = position.split(\"-\");\n return {\n [x]: `${offset[0]}px`,\n [y]: `${offset[1]}px`,\n };\n }, [position, offset]);\n\n useEffect(() => {\n console.log('scrollEntry', scrollEntry);\n if (scrollEntry && scrollEntry.scroller) {\n runtime.scrollListenerRemover?.();\n const onScroll = () => {\n const { scrollContainer } = scrollEntry;\n const curVisible = !!(\n scrollContainer &&\n (scrollContainer as HTMLElement).scrollTop > scrollTop\n );\n if (runtime.preVisible !== curVisible) {\n runtime.preVisible = curVisible;\n setVisible(curVisible);\n }\n };\n onScroll();\n runtime.scrollListenerRemover = addEventListener(\n scrollEntry.scroller,\n \"scroll\",\n onScroll\n );\n return () => {\n runtime.scrollListenerRemover?.();\n };\n }\n }, [scrollEntry.scroller]);\n\n return (\n <div\n title={title}\n ref={domRef}\n style={positionStyle}\n className={cx(\n c(\"container\", {\n visible: !!visible,\n invisible: !visible,\n }),\n c({ absolute: mountType === \"absolute\" }),\n className\n )}\n onClick={() => {\n scrollEntry.scroller?.scrollTo({\n top: 0,\n behavior: \"smooth\",\n });\n }}\n >\n {children || '↑'}\n </div>\n );\n};\n"],"names":["c","p","BackTop","_ref","children","className","_ref$scrollTop","scrollTop","_ref$mountType","mountType","_ref$position","position","_ref$offset","offset","_ref$title","title","scrollRefDom","_ref$realScroll","realScroll","_useState","useState","visible","setVisible","domRef","useRef","runtime","useRuntime","preVisible","scrollListenerRemover","scrollEntry","useScrollEntry","autoFind","intervalTime","positionStyle","useMemo","_ref2","_position$split","split","y","useEffect","console","log","scroller","onScroll","scrollContainer","curVisible","addEventListener","React","createElement","ref","style","cx","invisible","absolute","onClick","_scrollEntry$scroller","scrollTo","top","behavior"],"mappings":"gRAUa,IAAAA,EAAIC,EAAE,oBAuCNC,EAAU,SAAHC,GAUD,IATjBC,EAAQD,EAARC,SACAC,EAASF,EAATE,UAASC,EAAAH,EACTI,UAAAA,OAAY,IAAHD,EAAG,IAAGA,EAAAE,EAAAL,EACfM,UAAAA,OAAY,IAAHD,EAAG,QAAOA,EAAAE,EAAAP,EACnBQ,SAAAA,OAAW,IAAHD,EAAG,eAAcA,EAAAE,EAAAT,EACzBU,OAAAA,OAAM,IAAAD,EAAG,CAAC,GAAI,KAAIA,EAAAE,EAAAX,EAClBY,MAAAA,OAAK,IAAAD,EAAG,OAAMA,EACdE,EAAYb,EAAZa,aAAYC,EAAAd,EACZe,WAAAA,OAAU,IAAAD,GAAQA,EAElBE,EAA8BC,GAAS,GAAhCC,EAAOF,EAAA,GAAEG,EAAUH,EAAA,GACpBI,EAASC,EAAuB,MAC/BC,EAAWC,EAGf,CACDC,WAAYN,EACZO,sBAAuB,OALX,GAQRC,EAAcC,EAAed,GAA8BO,EAAQ,CACvEL,WAAAA,EACAa,UAAU,EACVC,aAAc,MAGVC,EAAgBC,EAAQ,WAAK,IAAAC,EACjCC,EAAezB,EAAS0B,MAAM,KAApBC,EAACF,EACX,GAAA,OAAAD,EAAA,CAAA,GADQC,EAAEE,IAEAzB,EAAO,GAAEsB,KAAAA,EAChBG,GAAOzB,EAAO,GAAE,KAAAsB,CAErB,EAAG,CAACxB,EAAUE,IA6Bd,OA3BA0B,EAAU,WAER,GADAC,QAAQC,IAAI,cAAeZ,GACvBA,GAAeA,EAAYa,SAAU,CACV,MAA7BjB,EAAQG,uBAARH,EAAQG,wBACR,IAAMe,EAAW,WACf,IAAQC,EAAoBf,EAApBe,gBACFC,KACJD,GACCA,EAAgCrC,UAAYA,GAE3CkB,EAAQE,aAAekB,IACzBpB,EAAQE,WAAakB,EACrBvB,EAAWuB,GAEf,EAOA,OANAF,IACAlB,EAAQG,sBAAwBkB,EAC9BjB,EAAYa,SACZ,SACAC,GAEK,WACLlB,MAAAA,EAAQG,uBAARH,EAAQG,uBACV,CACD,CACH,EAAG,CAACC,EAAYa,wBAGZK,EAAAC,cAAA,MAAA,CACEjC,MAAOA,EACPkC,IAAK1B,EACL2B,MAAOjB,EACP5B,UAAW8C,EACTnD,EAAE,YAAa,CACbqB,UAAWA,EACX+B,WAAY/B,IAEdrB,EAAE,CAAEqD,SAAwB,aAAd5C,IACdJ,GAEFiD,QAAS,WAAK,IAAAC,EACZA,OAAAA,EAAA1B,EAAYa,WAAZa,EAAsBC,SAAS,CAC7BC,IAAK,EACLC,SAAU,UAEd,GAECtD,GAAY,IAGrB"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var e=require("react"),r=require("@ohkit/prefix-classname"),l=require("@ohkit/react-helper"),o=require("@ohkit/dom-helper");function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i=/*#__PURE__*/t(e),s=r.prefixClassname("ohkit-back-top__");exports.BackTop=function(t){var n=t.children,c=t.className,a=t.scrollTop,u=void 0===a?500:a,v=t.mountType,f=void 0===v?"fixed":v,m=t.position,p=void 0===m?"bottom-right":m,d=t.offset,b=void 0===d?[50,100]:d,h=t.title,R=void 0===h?"返回顶部":h,k=t.scrollRefDom,x=t.realScroll,L=void 0!==x&&x,T=e.useState(!1),y=T[0],E=T[1],q=e.useRef(null),S=l.useRuntime({preVisible:y,scrollListenerRemover:null})[0],C=l.useScrollEntry(k||q,{realScroll:L,autoFind:!0,intervalTime:3e3}),N=e.useMemo(function(){var e,r=p.split("-"),l=r[1];return(e={})[r[0]]=b[0]+"px",e[l]=b[1]+"px",e},[p,b]);return e.useEffect(function(){if(console.log("scrollEntry",C),C&&C.scroller){null==S.scrollListenerRemover||S.scrollListenerRemover();var e=function(){var e=C.scrollContainer,r=!!(e&&e.scrollTop>u);S.preVisible!==r&&(S.preVisible=r,E(r))};return e(),S.scrollListenerRemover=o.addEventListener(C.scroller,"scroll",e),function(){null==S.scrollListenerRemover||S.scrollListenerRemover()}}},[C.scroller]),/*#__PURE__*/i.default.createElement("div",{title:R,ref:q,style:N,className:r.classNames(s("container",{visible:!!y,invisible:!y}),s({absolute:"absolute"===f}),c),onClick:function(){var e;null==(e=C.scroller)||e.scrollTo({top:0,behavior:"smooth"})}},n||"↑")},exports.c=s;
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.tsx"],"sourcesContent":["import React, { PropsWithChildren, useEffect, useState, useMemo, useRef } from \"react\";\nimport {\n prefixClassname as p,\n classNames as cx,\n} from \"@ohkit/prefix-classname\";\nimport { useRuntime, useScrollEntry } from \"@ohkit/react-helper\";\nimport { addEventListener } from \"@ohkit/dom-helper\";\n\nimport \"./style.scss\";\n\nexport const c = p(\"ohkit-back-top__\");\n\nexport interface IBackTop {\n className?: string;\n /**\n * 按钮位置,fixed 或 absolute\n * @default \"fixed\"\n */\n mountType?: \"fixed\" | \"absolute\";\n /**\n * 滚动距离,当滚动距离大于此值时显示返回顶部按钮\n * @default 500\n */\n scrollTop?: number;\n scrollRefDom?: React.MutableRefObject<HTMLElement>;\n /**\n * 按钮位置,top-right 或 top-left 或 bottom-right 或 bottom-left\n * @default \"bottom-right\"\n */\n position?: \"top-right\" | \"top-left\" | \"bottom-right\" | \"bottom-left\";\n /**\n * 按钮位置偏移量px, 即[position.split(\"-\")[0], position.split(\"-\")[1]]\n * @default[50, 100]\n */\n offset?: [number, number];\n /**\n * 是否查找真实可滚动的容器,scrollHeight > clientHeight\n * @default false\n */\n realScroll?: boolean;\n /**\n * 按钮标题\n * @default \"返回顶部\"\n */\n title?: string;\n}\n\nexport type BackTopProps = PropsWithChildren<IBackTop>;\n\nexport const BackTop = ({\n children,\n className,\n scrollTop = 500,\n mountType = \"fixed\",\n position = \"bottom-right\",\n offset = [50, 100],\n title = \"返回顶部\",\n scrollRefDom,\n realScroll = false,\n}: BackTopProps) => {\n const [visible, setVisible] = useState(false);\n const domRef = useRef<HTMLDivElement>(null);\n const [runtime] = useRuntime<{\n preVisible: boolean;\n scrollListenerRemover: null | (() => void);\n }>({\n preVisible: visible,\n scrollListenerRemover: null,\n });\n\n const scrollEntry = useScrollEntry(scrollRefDom ? scrollRefDom : domRef, {\n realScroll,\n autoFind: true,\n intervalTime: 3000,\n });\n\n const positionStyle = useMemo(() => {\n const [x, y] = position.split(\"-\");\n return {\n [x]: `${offset[0]}px`,\n [y]: `${offset[1]}px`,\n };\n }, [position, offset]);\n\n useEffect(() => {\n console.log('scrollEntry', scrollEntry);\n if (scrollEntry && scrollEntry.scroller) {\n runtime.scrollListenerRemover?.();\n const onScroll = () => {\n const { scrollContainer } = scrollEntry;\n const curVisible = !!(\n scrollContainer &&\n (scrollContainer as HTMLElement).scrollTop > scrollTop\n );\n if (runtime.preVisible !== curVisible) {\n runtime.preVisible = curVisible;\n setVisible(curVisible);\n }\n };\n onScroll();\n runtime.scrollListenerRemover = addEventListener(\n scrollEntry.scroller,\n \"scroll\",\n onScroll\n );\n return () => {\n runtime.scrollListenerRemover?.();\n };\n }\n }, [scrollEntry.scroller]);\n\n return (\n <div\n title={title}\n ref={domRef}\n style={positionStyle}\n className={cx(\n c(\"container\", {\n visible: !!visible,\n invisible: !visible,\n }),\n c({ absolute: mountType === \"absolute\" }),\n className\n )}\n onClick={() => {\n scrollEntry.scroller?.scrollTo({\n top: 0,\n behavior: \"smooth\",\n });\n }}\n >\n {children || '↑'}\n </div>\n );\n};\n"],"names":["c","p","prefixClassname","_ref","children","className","_ref$scrollTop","scrollTop","_ref$mountType","mountType","_ref$position","position","_ref$offset","offset","_ref$title","title","scrollRefDom","_ref$realScroll","realScroll","_useState","useState","visible","setVisible","domRef","useRef","runtime","useRuntime","preVisible","scrollListenerRemover","scrollEntry","useScrollEntry","autoFind","intervalTime","positionStyle","useMemo","_ref2","_position$split","split","y","useEffect","console","log","scroller","onScroll","scrollContainer","curVisible","addEventListener","React","createElement","ref","style","cx","invisible","absolute","onClick","_scrollEntry$scroller","scrollTo","top","behavior"],"mappings":"4NAUaA,EAAIC,EAACC,gBAAC,oCAuCI,SAAHC,GAUD,IATjBC,EAAQD,EAARC,SACAC,EAASF,EAATE,UAASC,EAAAH,EACTI,UAAAA,OAAY,IAAHD,EAAG,IAAGA,EAAAE,EAAAL,EACfM,UAAAA,OAAY,IAAHD,EAAG,QAAOA,EAAAE,EAAAP,EACnBQ,SAAAA,OAAW,IAAHD,EAAG,eAAcA,EAAAE,EAAAT,EACzBU,OAAAA,OAAM,IAAAD,EAAG,CAAC,GAAI,KAAIA,EAAAE,EAAAX,EAClBY,MAAAA,OAAK,IAAAD,EAAG,OAAMA,EACdE,EAAYb,EAAZa,aAAYC,EAAAd,EACZe,WAAAA,OAAU,IAAAD,GAAQA,EAElBE,EAA8BC,EAAAA,UAAS,GAAhCC,EAAOF,EAAA,GAAEG,EAAUH,EAAA,GACpBI,EAASC,EAAAA,OAAuB,MAC/BC,EAAWC,EAAAA,WAGf,CACDC,WAAYN,EACZO,sBAAuB,OALX,GAQRC,EAAcC,EAAcA,eAACd,GAA8BO,EAAQ,CACvEL,WAAAA,EACAa,UAAU,EACVC,aAAc,MAGVC,EAAgBC,EAAOA,QAAC,WAAK,IAAAC,EACjCC,EAAezB,EAAS0B,MAAM,KAApBC,EAACF,EACX,GAAA,OAAAD,EAAA,CAAA,GADQC,EAAEE,IAEAzB,EAAO,GAAEsB,KAAAA,EAChBG,GAAOzB,EAAO,GAAE,KAAAsB,CAErB,EAAG,CAACxB,EAAUE,IA6Bd,OA3BA0B,EAASA,UAAC,WAER,GADAC,QAAQC,IAAI,cAAeZ,GACvBA,GAAeA,EAAYa,SAAU,CACV,MAA7BjB,EAAQG,uBAARH,EAAQG,wBACR,IAAMe,EAAW,WACf,IAAQC,EAAoBf,EAApBe,gBACFC,KACJD,GACCA,EAAgCrC,UAAYA,GAE3CkB,EAAQE,aAAekB,IACzBpB,EAAQE,WAAakB,EACrBvB,EAAWuB,GAEf,EAOA,OANAF,IACAlB,EAAQG,sBAAwBkB,EAAgBA,iBAC9CjB,EAAYa,SACZ,SACAC,GAEK,WACLlB,MAAAA,EAAQG,uBAARH,EAAQG,uBACV,CACD,CACH,EAAG,CAACC,EAAYa,wBAGZK,EAAA,QAAAC,cAAA,MAAA,CACEjC,MAAOA,EACPkC,IAAK1B,EACL2B,MAAOjB,EACP5B,UAAW8C,EAAAA,WACTnD,EAAE,YAAa,CACbqB,UAAWA,EACX+B,WAAY/B,IAEdrB,EAAE,CAAEqD,SAAwB,aAAd5C,IACdJ,GAEFiD,QAAS,WAAK,IAAAC,EACZA,OAAAA,EAAA1B,EAAYa,WAAZa,EAAsBC,SAAS,CAC7BC,IAAK,EACLC,SAAU,UAEd,GAECtD,GAAY,IAGrB"}
@@ -0,0 +1,2 @@
1
+ import e,{useState as l,useRef as r,useMemo as o,useEffect as t}from"react";import{prefixClassname as s,classNames as i}from"@ohkit/prefix-classname";import{useRuntime as c,useScrollEntry as n}from"@ohkit/react-helper";import{addEventListener as m}from"@ohkit/dom-helper";const a=s("ohkit-back-top__"),p=({children:s,className:p,scrollTop:u=500,mountType:v="fixed",position:f="bottom-right",offset:b=[50,100],title:h="返回顶部",scrollRefDom:R,realScroll:k=!1})=>{const[L,d]=l(!1),x=r(null),[T]=c({preVisible:L,scrollListenerRemover:null}),y=n(R||x,{realScroll:k,autoFind:!0,intervalTime:3e3}),V=o(()=>{const[e,l]=f.split("-");return{[e]:`${b[0]}px`,[l]:`${b[1]}px`}},[f,b]);return t(()=>{if(console.log("scrollEntry",y),y&&y.scroller){null==T.scrollListenerRemover||T.scrollListenerRemover();const e=()=>{const{scrollContainer:e}=y,l=!!(e&&e.scrollTop>u);T.preVisible!==l&&(T.preVisible=l,d(l))};return e(),T.scrollListenerRemover=m(y.scroller,"scroll",e),()=>{null==T.scrollListenerRemover||T.scrollListenerRemover()}}},[y.scroller]),/*#__PURE__*/e.createElement("div",{title:h,ref:x,style:V,className:i(a("container",{visible:!!L,invisible:!L}),a({absolute:"absolute"===v}),p),onClick:()=>{var e;null==(e=y.scroller)||e.scrollTo({top:0,behavior:"smooth"})}},s||"↑")};export{p as BackTop,a as c};
2
+ //# sourceMappingURL=index.modern.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.modern.mjs","sources":["../src/index.tsx"],"sourcesContent":["import React, { PropsWithChildren, useEffect, useState, useMemo, useRef } from \"react\";\nimport {\n prefixClassname as p,\n classNames as cx,\n} from \"@ohkit/prefix-classname\";\nimport { useRuntime, useScrollEntry } from \"@ohkit/react-helper\";\nimport { addEventListener } from \"@ohkit/dom-helper\";\n\nimport \"./style.scss\";\n\nexport const c = p(\"ohkit-back-top__\");\n\nexport interface IBackTop {\n className?: string;\n /**\n * 按钮位置,fixed 或 absolute\n * @default \"fixed\"\n */\n mountType?: \"fixed\" | \"absolute\";\n /**\n * 滚动距离,当滚动距离大于此值时显示返回顶部按钮\n * @default 500\n */\n scrollTop?: number;\n scrollRefDom?: React.MutableRefObject<HTMLElement>;\n /**\n * 按钮位置,top-right 或 top-left 或 bottom-right 或 bottom-left\n * @default \"bottom-right\"\n */\n position?: \"top-right\" | \"top-left\" | \"bottom-right\" | \"bottom-left\";\n /**\n * 按钮位置偏移量px, 即[position.split(\"-\")[0], position.split(\"-\")[1]]\n * @default[50, 100]\n */\n offset?: [number, number];\n /**\n * 是否查找真实可滚动的容器,scrollHeight > clientHeight\n * @default false\n */\n realScroll?: boolean;\n /**\n * 按钮标题\n * @default \"返回顶部\"\n */\n title?: string;\n}\n\nexport type BackTopProps = PropsWithChildren<IBackTop>;\n\nexport const BackTop = ({\n children,\n className,\n scrollTop = 500,\n mountType = \"fixed\",\n position = \"bottom-right\",\n offset = [50, 100],\n title = \"返回顶部\",\n scrollRefDom,\n realScroll = false,\n}: BackTopProps) => {\n const [visible, setVisible] = useState(false);\n const domRef = useRef<HTMLDivElement>(null);\n const [runtime] = useRuntime<{\n preVisible: boolean;\n scrollListenerRemover: null | (() => void);\n }>({\n preVisible: visible,\n scrollListenerRemover: null,\n });\n\n const scrollEntry = useScrollEntry(scrollRefDom ? scrollRefDom : domRef, {\n realScroll,\n autoFind: true,\n intervalTime: 3000,\n });\n\n const positionStyle = useMemo(() => {\n const [x, y] = position.split(\"-\");\n return {\n [x]: `${offset[0]}px`,\n [y]: `${offset[1]}px`,\n };\n }, [position, offset]);\n\n useEffect(() => {\n console.log('scrollEntry', scrollEntry);\n if (scrollEntry && scrollEntry.scroller) {\n runtime.scrollListenerRemover?.();\n const onScroll = () => {\n const { scrollContainer } = scrollEntry;\n const curVisible = !!(\n scrollContainer &&\n (scrollContainer as HTMLElement).scrollTop > scrollTop\n );\n if (runtime.preVisible !== curVisible) {\n runtime.preVisible = curVisible;\n setVisible(curVisible);\n }\n };\n onScroll();\n runtime.scrollListenerRemover = addEventListener(\n scrollEntry.scroller,\n \"scroll\",\n onScroll\n );\n return () => {\n runtime.scrollListenerRemover?.();\n };\n }\n }, [scrollEntry.scroller]);\n\n return (\n <div\n title={title}\n ref={domRef}\n style={positionStyle}\n className={cx(\n c(\"container\", {\n visible: !!visible,\n invisible: !visible,\n }),\n c({ absolute: mountType === \"absolute\" }),\n className\n )}\n onClick={() => {\n scrollEntry.scroller?.scrollTo({\n top: 0,\n behavior: \"smooth\",\n });\n }}\n >\n {children || '↑'}\n </div>\n );\n};\n"],"names":["c","p","BackTop","children","className","scrollTop","mountType","position","offset","title","scrollRefDom","realScroll","visible","setVisible","useState","domRef","useRef","runtime","useRuntime","preVisible","scrollListenerRemover","scrollEntry","useScrollEntry","autoFind","intervalTime","positionStyle","useMemo","x","y","split","useEffect","console","log","scroller","onScroll","scrollContainer","curVisible","addEventListener","React","createElement","ref","style","cx","invisible","absolute","onClick","_scrollEntry$scroller","scrollTo","top","behavior"],"mappings":"gRAUa,MAAAA,EAAIC,EAAE,oBAuCNC,EAAUA,EACrBC,WACAC,YACAC,UAAAA,EAAY,IACZC,UAAAA,EAAY,QACZC,SAAAA,EAAW,eACXC,OAAAA,EAAS,CAAC,GAAI,KACdC,MAAAA,EAAQ,OACRC,eACAC,WAAAA,GAAa,MAEb,MAAOC,EAASC,GAAcC,GAAS,GACjCC,EAASC,EAAuB,OAC/BC,GAAWC,EAGf,CACDC,WAAYP,EACZQ,sBAAuB,OAGnBC,EAAcC,EAAeZ,GAA8BK,EAAQ,CACvEJ,WAAAA,EACAY,UAAU,EACVC,aAAc,MAGVC,EAAgBC,EAAQ,KAC5B,MAAOC,EAAGC,GAAKrB,EAASsB,MAAM,KAC9B,MAAO,CACLF,CAACA,GAAI,GAAGnB,EAAO,OACfoB,CAACA,GAAI,GAAGpB,EAAO,SAEhB,CAACD,EAAUC,IA6Bd,OA3BAsB,EAAU,KAER,GADAC,QAAQC,IAAI,cAAeX,GACvBA,GAAeA,EAAYY,SAAU,CACvChB,MAAAA,EAAQG,uBAARH,EAAQG,wBACR,MAAMc,EAAWA,KACf,MAAMC,gBAAEA,GAAoBd,EACtBe,KACJD,GACCA,EAAgC9B,UAAYA,GAE3CY,EAAQE,aAAeiB,IACzBnB,EAAQE,WAAaiB,EACrBvB,EAAWuB,KASf,OANAF,IACAjB,EAAQG,sBAAwBiB,EAC9BhB,EAAYY,SACZ,SACAC,GAEK,KACLjB,MAAAA,EAAQG,uBAARH,EAAQG,wBAEX,GACA,CAACC,EAAYY,wBAGZK,EAAAC,cACE9B,MAAAA,CAAAA,MAAOA,EACP+B,IAAKzB,EACL0B,MAAOhB,EACPrB,UAAWsC,EACT1C,EAAE,YAAa,CACbY,UAAWA,EACX+B,WAAY/B,IAEdZ,EAAE,CAAE4C,SAAwB,aAAdtC,IACdF,GAEFyC,QAASA,KAAKC,IAAAA,EACZA,OAAAA,EAAAzB,EAAYY,WAAZa,EAAsBC,SAAS,CAC7BC,IAAK,EACLC,SAAU,aAIb9C,GAAY"}
@@ -0,0 +1,2 @@
1
+ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react"),require("@ohkit/prefix-classname"),require("@ohkit/react-helper"),require("@ohkit/dom-helper")):"function"==typeof define&&define.amd?define(["exports","react","@ohkit/prefix-classname","@ohkit/react-helper","@ohkit/dom-helper"],r):r((e||self).backTop={},e.react,e.prefixClassname,e.reactHelper,e.domHelper)}(this,function(e,r,o,l,t){function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=/*#__PURE__*/i(r),s=o.prefixClassname("ohkit-back-top__");e.BackTop=function(e){var i=e.children,c=e.className,a=e.scrollTop,u=void 0===a?500:a,f=e.mountType,p=void 0===f?"fixed":f,d=e.position,m=void 0===d?"bottom-right":d,v=e.offset,h=void 0===v?[50,100]:v,b=e.title,k=void 0===b?"返回顶部":b,x=e.scrollRefDom,y=e.realScroll,R=void 0!==y&&y,T=r.useState(!1),L=T[0],E=T[1],g=r.useRef(null),q=l.useRuntime({preVisible:L,scrollListenerRemover:null})[0],C=l.useScrollEntry(x||g,{realScroll:R,autoFind:!0,intervalTime:3e3}),S=r.useMemo(function(){var e,r=m.split("-"),o=r[1];return(e={})[r[0]]=h[0]+"px",e[o]=h[1]+"px",e},[m,h]);return r.useEffect(function(){if(console.log("scrollEntry",C),C&&C.scroller){null==q.scrollListenerRemover||q.scrollListenerRemover();var e=function(){var e=C.scrollContainer,r=!!(e&&e.scrollTop>u);q.preVisible!==r&&(q.preVisible=r,E(r))};return e(),q.scrollListenerRemover=t.addEventListener(C.scroller,"scroll",e),function(){null==q.scrollListenerRemover||q.scrollListenerRemover()}}},[C.scroller]),/*#__PURE__*/n.default.createElement("div",{title:k,ref:g,style:S,className:o.classNames(s("container",{visible:!!L,invisible:!L}),s({absolute:"absolute"===p}),c),onClick:function(){var e;null==(e=C.scroller)||e.scrollTo({top:0,behavior:"smooth"})}},i||"↑")},e.c=s});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/index.tsx"],"sourcesContent":["import React, { PropsWithChildren, useEffect, useState, useMemo, useRef } from \"react\";\nimport {\n prefixClassname as p,\n classNames as cx,\n} from \"@ohkit/prefix-classname\";\nimport { useRuntime, useScrollEntry } from \"@ohkit/react-helper\";\nimport { addEventListener } from \"@ohkit/dom-helper\";\n\nimport \"./style.scss\";\n\nexport const c = p(\"ohkit-back-top__\");\n\nexport interface IBackTop {\n className?: string;\n /**\n * 按钮位置,fixed 或 absolute\n * @default \"fixed\"\n */\n mountType?: \"fixed\" | \"absolute\";\n /**\n * 滚动距离,当滚动距离大于此值时显示返回顶部按钮\n * @default 500\n */\n scrollTop?: number;\n scrollRefDom?: React.MutableRefObject<HTMLElement>;\n /**\n * 按钮位置,top-right 或 top-left 或 bottom-right 或 bottom-left\n * @default \"bottom-right\"\n */\n position?: \"top-right\" | \"top-left\" | \"bottom-right\" | \"bottom-left\";\n /**\n * 按钮位置偏移量px, 即[position.split(\"-\")[0], position.split(\"-\")[1]]\n * @default[50, 100]\n */\n offset?: [number, number];\n /**\n * 是否查找真实可滚动的容器,scrollHeight > clientHeight\n * @default false\n */\n realScroll?: boolean;\n /**\n * 按钮标题\n * @default \"返回顶部\"\n */\n title?: string;\n}\n\nexport type BackTopProps = PropsWithChildren<IBackTop>;\n\nexport const BackTop = ({\n children,\n className,\n scrollTop = 500,\n mountType = \"fixed\",\n position = \"bottom-right\",\n offset = [50, 100],\n title = \"返回顶部\",\n scrollRefDom,\n realScroll = false,\n}: BackTopProps) => {\n const [visible, setVisible] = useState(false);\n const domRef = useRef<HTMLDivElement>(null);\n const [runtime] = useRuntime<{\n preVisible: boolean;\n scrollListenerRemover: null | (() => void);\n }>({\n preVisible: visible,\n scrollListenerRemover: null,\n });\n\n const scrollEntry = useScrollEntry(scrollRefDom ? scrollRefDom : domRef, {\n realScroll,\n autoFind: true,\n intervalTime: 3000,\n });\n\n const positionStyle = useMemo(() => {\n const [x, y] = position.split(\"-\");\n return {\n [x]: `${offset[0]}px`,\n [y]: `${offset[1]}px`,\n };\n }, [position, offset]);\n\n useEffect(() => {\n console.log('scrollEntry', scrollEntry);\n if (scrollEntry && scrollEntry.scroller) {\n runtime.scrollListenerRemover?.();\n const onScroll = () => {\n const { scrollContainer } = scrollEntry;\n const curVisible = !!(\n scrollContainer &&\n (scrollContainer as HTMLElement).scrollTop > scrollTop\n );\n if (runtime.preVisible !== curVisible) {\n runtime.preVisible = curVisible;\n setVisible(curVisible);\n }\n };\n onScroll();\n runtime.scrollListenerRemover = addEventListener(\n scrollEntry.scroller,\n \"scroll\",\n onScroll\n );\n return () => {\n runtime.scrollListenerRemover?.();\n };\n }\n }, [scrollEntry.scroller]);\n\n return (\n <div\n title={title}\n ref={domRef}\n style={positionStyle}\n className={cx(\n c(\"container\", {\n visible: !!visible,\n invisible: !visible,\n }),\n c({ absolute: mountType === \"absolute\" }),\n className\n )}\n onClick={() => {\n scrollEntry.scroller?.scrollTo({\n top: 0,\n behavior: \"smooth\",\n });\n }}\n >\n {children || '↑'}\n </div>\n );\n};\n"],"names":["c","p","prefixClassname","_ref","children","className","_ref$scrollTop","scrollTop","_ref$mountType","mountType","_ref$position","position","_ref$offset","offset","_ref$title","title","scrollRefDom","_ref$realScroll","realScroll","_useState","useState","visible","setVisible","domRef","useRef","runtime","useRuntime","preVisible","scrollListenerRemover","scrollEntry","useScrollEntry","autoFind","intervalTime","positionStyle","useMemo","_ref2","_position$split","split","y","useEffect","console","log","scroller","onScroll","scrollContainer","curVisible","addEventListener","React","createElement","ref","style","cx","invisible","absolute","onClick","_scrollEntry$scroller","scrollTo","top","behavior"],"mappings":"yjBAUaA,EAAIC,EAACC,gBAAC,8BAuCI,SAAHC,GAUD,IATjBC,EAAQD,EAARC,SACAC,EAASF,EAATE,UAASC,EAAAH,EACTI,UAAAA,OAAY,IAAHD,EAAG,IAAGA,EAAAE,EAAAL,EACfM,UAAAA,OAAY,IAAHD,EAAG,QAAOA,EAAAE,EAAAP,EACnBQ,SAAAA,OAAW,IAAHD,EAAG,eAAcA,EAAAE,EAAAT,EACzBU,OAAAA,OAAM,IAAAD,EAAG,CAAC,GAAI,KAAIA,EAAAE,EAAAX,EAClBY,MAAAA,OAAK,IAAAD,EAAG,OAAMA,EACdE,EAAYb,EAAZa,aAAYC,EAAAd,EACZe,WAAAA,OAAU,IAAAD,GAAQA,EAElBE,EAA8BC,EAAAA,UAAS,GAAhCC,EAAOF,EAAA,GAAEG,EAAUH,EAAA,GACpBI,EAASC,EAAAA,OAAuB,MAC/BC,EAAWC,EAAAA,WAGf,CACDC,WAAYN,EACZO,sBAAuB,OALX,GAQRC,EAAcC,EAAcA,eAACd,GAA8BO,EAAQ,CACvEL,WAAAA,EACAa,UAAU,EACVC,aAAc,MAGVC,EAAgBC,EAAOA,QAAC,WAAK,IAAAC,EACjCC,EAAezB,EAAS0B,MAAM,KAApBC,EAACF,EACX,GAAA,OAAAD,EAAA,CAAA,GADQC,EAAEE,IAEAzB,EAAO,GAAEsB,KAAAA,EAChBG,GAAOzB,EAAO,GAAE,KAAAsB,CAErB,EAAG,CAACxB,EAAUE,IA6Bd,OA3BA0B,EAASA,UAAC,WAER,GADAC,QAAQC,IAAI,cAAeZ,GACvBA,GAAeA,EAAYa,SAAU,CACV,MAA7BjB,EAAQG,uBAARH,EAAQG,wBACR,IAAMe,EAAW,WACf,IAAQC,EAAoBf,EAApBe,gBACFC,KACJD,GACCA,EAAgCrC,UAAYA,GAE3CkB,EAAQE,aAAekB,IACzBpB,EAAQE,WAAakB,EACrBvB,EAAWuB,GAEf,EAOA,OANAF,IACAlB,EAAQG,sBAAwBkB,EAAgBA,iBAC9CjB,EAAYa,SACZ,SACAC,GAEK,WACLlB,MAAAA,EAAQG,uBAARH,EAAQG,uBACV,CACD,CACH,EAAG,CAACC,EAAYa,wBAGZK,EAAA,QAAAC,cAAA,MAAA,CACEjC,MAAOA,EACPkC,IAAK1B,EACL2B,MAAOjB,EACP5B,UAAW8C,EAAAA,WACTnD,EAAE,YAAa,CACbqB,UAAWA,EACX+B,WAAY/B,IAEdrB,EAAE,CAAEqD,SAAwB,aAAd5C,IACdJ,GAEFiD,QAAS,WAAK,IAAAC,EACZA,OAAAA,EAAA1B,EAAYa,WAAZa,EAAsBC,SAAS,CAC7BC,IAAK,EACLC,SAAU,UAEd,GAECtD,GAAY,IAGrB"}
@@ -0,0 +1,40 @@
1
+ import React, { PropsWithChildren } from "react";
2
+ import { classNames as cx } from "@ohkit/prefix-classname";
3
+ import "./style.scss";
4
+ export declare const c: (...arg: cx.ArgumentArray) => string;
5
+ export interface IBackTop {
6
+ className?: string;
7
+ /**
8
+ * 按钮位置,fixed 或 absolute
9
+ * @default "fixed"
10
+ */
11
+ mountType?: "fixed" | "absolute";
12
+ /**
13
+ * 滚动距离,当滚动距离大于此值时显示返回顶部按钮
14
+ * @default 500
15
+ */
16
+ scrollTop?: number;
17
+ scrollRefDom?: React.MutableRefObject<HTMLElement>;
18
+ /**
19
+ * 按钮位置,top-right 或 top-left 或 bottom-right 或 bottom-left
20
+ * @default "bottom-right"
21
+ */
22
+ position?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
23
+ /**
24
+ * 按钮位置偏移量px, 即[position.split("-")[0], position.split("-")[1]]
25
+ * @default[50, 100]
26
+ */
27
+ offset?: [number, number];
28
+ /**
29
+ * 是否查找真实可滚动的容器,scrollHeight > clientHeight
30
+ * @default false
31
+ */
32
+ realScroll?: boolean;
33
+ /**
34
+ * 按钮标题
35
+ * @default "返回顶部"
36
+ */
37
+ title?: string;
38
+ }
39
+ export type BackTopProps = PropsWithChildren<IBackTop>;
40
+ export declare const BackTop: ({ children, className, scrollTop, mountType, position, offset, title, scrollRefDom, realScroll, }: BackTopProps) => React.JSX.Element;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@ohkit/back-top",
3
+ "version": "0.0.1",
4
+ "description": "列表滚动超过指定距离后自动展示返回顶部按钮",
5
+ "keywords": [
6
+ "back-top"
7
+ ],
8
+ "homepage": "https://wuqiuyang.github.io/ohkit/storybook-static",
9
+ "license": "ISC",
10
+ "source": "src/index.@(js|jsx|mjs|ts|tsx)",
11
+ "main": "dist/index.js",
12
+ "umd:main": "dist/index.umd.js",
13
+ "module": "dist/index.es.js",
14
+ "types": "dist/types/index.d.ts",
15
+ "publishConfig": {
16
+ "registry": "https://registry.npmjs.org/",
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/WuQiuYang/ohkit"
22
+ },
23
+ "scripts": {
24
+ "build": "npm run clean && microbundle --jsx React.createElement",
25
+ "dev": "npm run watch",
26
+ "watch": "microbundle watch --jsx React.createElement --no-compress --format es --output dist/index.es.js",
27
+ "clean": "rm -rf dist"
28
+ },
29
+ "dependencies": {
30
+ "@ohkit/dom-helper": "0.0.5",
31
+ "@ohkit/prefix-classname": "^0.0.3",
32
+ "@ohkit/react-helper": "0.0.5"
33
+ },
34
+ "peerDependencies": {
35
+ "react": ">=17",
36
+ "react-dom": ">=17"
37
+ },
38
+ "gitHead": "032c94e624cb5be095bae2032083c12a75230de6"
39
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,135 @@
1
+ import React, { PropsWithChildren, useEffect, useState, useMemo, useRef } from "react";
2
+ import {
3
+ prefixClassname as p,
4
+ classNames as cx,
5
+ } from "@ohkit/prefix-classname";
6
+ import { useRuntime, useScrollEntry } from "@ohkit/react-helper";
7
+ import { addEventListener } from "@ohkit/dom-helper";
8
+
9
+ import "./style.scss";
10
+
11
+ export const c = p("ohkit-back-top__");
12
+
13
+ export interface IBackTop {
14
+ className?: string;
15
+ /**
16
+ * 按钮位置,fixed 或 absolute
17
+ * @default "fixed"
18
+ */
19
+ mountType?: "fixed" | "absolute";
20
+ /**
21
+ * 滚动距离,当滚动距离大于此值时显示返回顶部按钮
22
+ * @default 500
23
+ */
24
+ scrollTop?: number;
25
+ scrollRefDom?: React.MutableRefObject<HTMLElement>;
26
+ /**
27
+ * 按钮位置,top-right 或 top-left 或 bottom-right 或 bottom-left
28
+ * @default "bottom-right"
29
+ */
30
+ position?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
31
+ /**
32
+ * 按钮位置偏移量px, 即[position.split("-")[0], position.split("-")[1]]
33
+ * @default[50, 100]
34
+ */
35
+ offset?: [number, number];
36
+ /**
37
+ * 是否查找真实可滚动的容器,scrollHeight > clientHeight
38
+ * @default false
39
+ */
40
+ realScroll?: boolean;
41
+ /**
42
+ * 按钮标题
43
+ * @default "返回顶部"
44
+ */
45
+ title?: string;
46
+ }
47
+
48
+ export type BackTopProps = PropsWithChildren<IBackTop>;
49
+
50
+ export const BackTop = ({
51
+ children,
52
+ className,
53
+ scrollTop = 500,
54
+ mountType = "fixed",
55
+ position = "bottom-right",
56
+ offset = [50, 100],
57
+ title = "返回顶部",
58
+ scrollRefDom,
59
+ realScroll = false,
60
+ }: BackTopProps) => {
61
+ const [visible, setVisible] = useState(false);
62
+ const domRef = useRef<HTMLDivElement>(null);
63
+ const [runtime] = useRuntime<{
64
+ preVisible: boolean;
65
+ scrollListenerRemover: null | (() => void);
66
+ }>({
67
+ preVisible: visible,
68
+ scrollListenerRemover: null,
69
+ });
70
+
71
+ const scrollEntry = useScrollEntry(scrollRefDom ? scrollRefDom : domRef, {
72
+ realScroll,
73
+ autoFind: true,
74
+ intervalTime: 3000,
75
+ });
76
+
77
+ const positionStyle = useMemo(() => {
78
+ const [x, y] = position.split("-");
79
+ return {
80
+ [x]: `${offset[0]}px`,
81
+ [y]: `${offset[1]}px`,
82
+ };
83
+ }, [position, offset]);
84
+
85
+ useEffect(() => {
86
+ console.log('scrollEntry', scrollEntry);
87
+ if (scrollEntry && scrollEntry.scroller) {
88
+ runtime.scrollListenerRemover?.();
89
+ const onScroll = () => {
90
+ const { scrollContainer } = scrollEntry;
91
+ const curVisible = !!(
92
+ scrollContainer &&
93
+ (scrollContainer as HTMLElement).scrollTop > scrollTop
94
+ );
95
+ if (runtime.preVisible !== curVisible) {
96
+ runtime.preVisible = curVisible;
97
+ setVisible(curVisible);
98
+ }
99
+ };
100
+ onScroll();
101
+ runtime.scrollListenerRemover = addEventListener(
102
+ scrollEntry.scroller,
103
+ "scroll",
104
+ onScroll
105
+ );
106
+ return () => {
107
+ runtime.scrollListenerRemover?.();
108
+ };
109
+ }
110
+ }, [scrollEntry.scroller]);
111
+
112
+ return (
113
+ <div
114
+ title={title}
115
+ ref={domRef}
116
+ style={positionStyle}
117
+ className={cx(
118
+ c("container", {
119
+ visible: !!visible,
120
+ invisible: !visible,
121
+ }),
122
+ c({ absolute: mountType === "absolute" }),
123
+ className
124
+ )}
125
+ onClick={() => {
126
+ scrollEntry.scroller?.scrollTo({
127
+ top: 0,
128
+ behavior: "smooth",
129
+ });
130
+ }}
131
+ >
132
+ {children || '↑'}
133
+ </div>
134
+ );
135
+ };
package/src/style.scss ADDED
@@ -0,0 +1,46 @@
1
+ $prefix: ohkit;
2
+
3
+ .#{$prefix}-back-top__ {
4
+ &container {
5
+ // 添加你的样式
6
+ position: fixed;
7
+ // right: 100px;
8
+ // bottom: 50px;
9
+ box-sizing: border-box;
10
+ padding: 4px;
11
+ min-width: 40px;
12
+ min-height: 40px;
13
+ line-height: 1.2em;
14
+ z-index: 10;
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ font-size: 14px;
19
+ color: #fff;
20
+ white-space: pre-line;
21
+ border-radius: 4px;
22
+ // border-radius: 50%;
23
+
24
+ transition: all 0.2s ease-in;
25
+ }
26
+ &absolute {
27
+ position: absolute;
28
+ right: 20px;
29
+ bottom: 20px;
30
+ }
31
+
32
+ &invisible {
33
+ pointer-events: none;
34
+ opacity: 0;
35
+ background: transparent;
36
+ }
37
+ &visible {
38
+ background: rgba(78, 85, 98, 0.25);
39
+ opacity: 1;
40
+ cursor: pointer;
41
+
42
+ &:hover {
43
+ background: rgba(78, 85, 98, 0.4);
44
+ }
45
+ }
46
+ }