Skip to content

Commit e670f2c

Browse files
committed
feat : 新增返回顶部按钮
1 parent b9fc3ba commit e670f2c

File tree

8 files changed

+165
-9
lines changed

8 files changed

+165
-9
lines changed

public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@
2323
<body>
2424
<div id="root"></div>
2525
</body>
26-
<script src="//at.alicdn.com/t/font_2728508_l8by2j35n3r.js"></script>
26+
<script src="//at.alicdn.com/t/font_2728508_6p9v1vaetjp.js"></script>
2727
</html>

src/Layout/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useState } from 'react';
22
import Header from '@/components/Header';
33
import Footer from '@/components/Footer';
4+
import Back from '@/components/BackToTop';
45
import styles from './index.less';
56
interface AppProps {
67
children: React.ReactNode;
@@ -10,6 +11,7 @@ const Layout: React.FC<AppProps> = ({ children }) => {
1011
<>
1112
<Header />
1213
<main className={styles.main}>{children}</main>
14+
<Back />
1315
<Footer />
1416
</>
1517
);

src/components/BackToTop/index.less

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.backBtn {
2+
position: fixed;
3+
right: 16px;
4+
bottom: 16px;
5+
background: #f7f8fb;
6+
background: var(--light-bg-color);
7+
border-radius: 3px;
8+
padding: 8px;
9+
color: #1a202b;
10+
color: var(--text-color-dark);
11+
opacity: 0;
12+
transition: all 0.25s;
13+
&:focus {
14+
color: #1a202b;
15+
color: var(--text-color-dark);
16+
color: #fff;
17+
color: var(--white);
18+
}
19+
&.show {
20+
opacity: 1;
21+
}
22+
&:hover {
23+
background-color: var(--theme-color);
24+
color: var(--white);
25+
}
26+
}

src/components/BackToTop/index.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { useEffect, useState } from 'react';
2+
import cs from 'classnames';
3+
import IconFont from '@/components/IconFont';
4+
import getScroll from '@/utils/scroll';
5+
import addEventListener from 'rc-util/lib/Dom/addEventListener';
6+
import styles from './index.less';
7+
import scroll from '@/utils/scrollTo';
8+
const index = () => {
9+
const [visible, setVisible] = useState(true);
10+
const ref = React.createRef<HTMLDivElement>();
11+
const scrollEvent = React.useRef<any>();
12+
13+
const getDefaultTarget = () =>
14+
ref.current && ref.current.ownerDocument ? ref.current.ownerDocument : window;
15+
16+
const handleScroll = (e: React.UIEvent<HTMLElement> | { target: any }) => {
17+
const scrollTop = getScroll(e.target, true);
18+
setVisible(scrollTop > 400!);
19+
};
20+
21+
const bindScrollEvent = () => {
22+
const getTarget = getDefaultTarget;
23+
const container = getTarget();
24+
scrollEvent.current = addEventListener(container, 'scroll', (e: React.UIEvent<HTMLElement>) => {
25+
handleScroll(e);
26+
});
27+
handleScroll({
28+
target: container,
29+
});
30+
};
31+
32+
useEffect(() => {
33+
bindScrollEvent();
34+
return () => {
35+
if (scrollEvent.current) {
36+
scrollEvent.current.remove();
37+
}
38+
(handleScroll as any).cancel();
39+
};
40+
}, [window]);
41+
42+
return (
43+
<div>
44+
<a
45+
className={cs(styles.backBtn, visible && styles.show)}
46+
onClick={() => {
47+
scroll(0);
48+
}}
49+
>
50+
<IconFont type="#icon-fanhuidingbu" />
51+
</a>
52+
</div>
53+
);
54+
};
55+
56+
export default index;

src/components/Header/index.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Menus from '../Menu';
66
const Header: React.FC = () => {
77
const [theme, setTheme] = useState('light');
88
const [visible, setVisible] = useState<boolean>(true);
9-
const [mobileMenu, setMobileMenu] = useState(false);
9+
const [collapse, setCollapse] = useState(true);
1010
const [scroll, setScroll] = useState(0);
1111
function handleTheme() {
1212
if (document.documentElement.dataset.theme === 'light') {
@@ -36,7 +36,7 @@ const Header: React.FC = () => {
3636
setVisible(true);
3737
} else {
3838
setVisible(false);
39-
setMobileMenu(false);
39+
setCollapse(true);
4040
}
4141
setTimeout(function () {
4242
topValue = scrollTop;
@@ -55,12 +55,12 @@ const Header: React.FC = () => {
5555
<div className={styles.container}>
5656
<div className={styles.title}>Litle Seed</div>
5757
<div className={styles.headerRight}>
58-
<Menus mobileMenu={mobileMenu} />
58+
<Menus collapse={collapse} />
5959
<IconFont onClick={handleTheme} type={theme === 'light' ? '#icon-moon' : '#icon-sun'} />
6060
<IconFont
61-
type="#icon-menu3"
61+
type={collapse ? '#icon-menu3' : '#icon-close1'}
6262
onClick={() => {
63-
setMobileMenu(!mobileMenu);
63+
setCollapse(!collapse);
6464
}}
6565
/>
6666
</div>

src/components/Menu/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import classnames from 'classnames/bind';
66
import { routeConfigType } from '@/typings/Menu';
77

88
const cx = classnames.bind(styles);
9-
const Menus: React.FC = ({ mobileMenu }: any) => {
10-
console.log(mobileMenu);
9+
const Menus: React.FC = ({ collapse }: any) => {
1110
const history = useHistory();
1211
const location = useLocation();
1312
function handleClick(item: routeConfigType) {
1413
history.push(item.path);
1514
}
1615
return (
17-
<nav className={cx('navWrap', { show: mobileMenu })}>
16+
<nav className={cx('navWrap', { show: !collapse })}>
1817
<ul className={styles.navList}>
1918
{RouteConfig.map((item, index) => {
2019
return (

src/utils/scroll.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export function isWindow(obj: any) {
2+
return obj !== null && obj !== undefined && obj === obj.window;
3+
}
4+
5+
export default function getScroll(
6+
target: HTMLElement | Window | Document | null,
7+
top: boolean,
8+
): number {
9+
if (typeof window === 'undefined') {
10+
return 0;
11+
}
12+
const method = top ? 'scrollTop' : 'scrollLeft';
13+
let result = 0;
14+
if (isWindow(target)) {
15+
result = (target as Window)[top ? 'pageYOffset' : 'pageXOffset'];
16+
} else if (target instanceof Document) {
17+
result = target.documentElement[method];
18+
} else if (target) {
19+
result = (target as HTMLElement)[method];
20+
}
21+
if (target && !isWindow(target) && typeof result !== 'number') {
22+
result = ((target as HTMLElement).ownerDocument || (target as Document)).documentElement?.[
23+
method
24+
];
25+
}
26+
return result;
27+
}

src/utils/scrollTo.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import raf from 'rc-util/lib/raf';
2+
import getScroll, { isWindow } from './scroll';
3+
4+
interface ScrollToOptions {
5+
/** Scroll container, default as window */
6+
getContainer?: () => HTMLElement | Window | Document;
7+
/** Scroll end callback */
8+
callback?: () => any;
9+
/** Animation duration, default as 450 */
10+
duration?: number;
11+
}
12+
13+
function easeInOutCubic(t: number, b: number, c: number, d: number) {
14+
const cc = c - b;
15+
t /= d / 2;
16+
if (t < 1) {
17+
return (cc / 2) * t * t * t + b;
18+
}
19+
// eslint-disable-next-line no-return-assign
20+
return (cc / 2) * ((t -= 2) * t * t + 2) + b;
21+
}
22+
export default function scrollTo(y: number, options: ScrollToOptions = {}) {
23+
const { getContainer = () => window, callback, duration = 450 } = options;
24+
const container = getContainer();
25+
const scrollTop = getScroll(container, true);
26+
const startTime = Date.now();
27+
28+
const frameFunc = () => {
29+
const timestamp = Date.now();
30+
const time = timestamp - startTime;
31+
const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration);
32+
if (isWindow(container)) {
33+
(container as Window).scrollTo(window.pageXOffset, nextScrollTop);
34+
} else if (container instanceof HTMLDocument || container.constructor.name === 'HTMLDocument') {
35+
(container as HTMLDocument).documentElement.scrollTop = nextScrollTop;
36+
} else {
37+
(container as HTMLElement).scrollTop = nextScrollTop;
38+
}
39+
if (time < duration) {
40+
raf(frameFunc);
41+
} else if (typeof callback === 'function') {
42+
callback();
43+
}
44+
};
45+
raf(frameFunc);
46+
}

0 commit comments

Comments
 (0)