背景
在最近的业务中,产品提了一个需求,在页面的右下角添加一个返回顶部的按钮。由于前端UI框架用的是 Ant Design Pro,因此很自然地去Ant Design 组件库寻找有没有类似的组件。Ant Design 提供了一个 BackTop 的组件,它就是用来返回页面顶部的。将这个组件引入到项目中,发现在项目代码中无法渲染出该组件,于是抛弃了它,自己实现一个返回顶部的按钮。
实现按钮
【回到顶部】按钮通常是固定在长页面的右下角,单击按钮的时候可以直接让页面回到顶部。因此我们应该将按钮的定位方式设为固定定位:position: fixed;
,并将其位置固定到页面右下角。
实现样式
我们先来看看实现按钮的样式:
#backToTop { position: fixed; right: 20px; bottom: 10px; width: 42px; height: 42px; background-color: #1088e9; color: #ffffff; border-radius: 4px; font-size: 40px; cursor: pointer; text-align: center; &:before { content: '^'; display: block; font-family: serif, 'Times New Roman', Times; } &:hover:before { font-size: 12px; content: '回顶部'; line-height: 42px; } }
组件结构
下面我们来实现最基本的组件结构:
import React from 'react'; import styles from './index.less'; interface BackToTopBtnProps { } const BackToTopBtn: React.FC<BackToTopBtnProps> = props => { return ( <div id={styles.backToTop}></div> ) }; export default BackToTopBtn;
控制按钮显隐
通常情况下,【返回顶部】按钮是不显示的,只有页面下拉到一定的高度时才显示。因此我们需要监听滚动事件,当页面向上滚动到一定的距离时,将按钮显示出来。现在,我们编写一个监听滚动事件的函数,结合 React 的 State Hooks 和 Effect Hooks 来控制按钮的显隐:
import React, { useEffect, useState } from 'react'; import styles from './index.less'; interface BackToTopBtnProps { } const BackToTopBtn: React.FC<BackToTopBtnProps> = props => { // 定义 visibleBackTopBtn 变量控制 返回顶部 按钮的显隐 const [visibleBackTopBtn, setVisibleBackTopBtn] = useState(false) useEffect(() => { // 在 React 中使用 addEventListener 监听事件 document.addEventListener('scroll', handleScroll, true); // 组件卸载时移除事件监听 return () => document.removeEventListener('scroll', handleScroll) }, [visibleBackTopBtn]) // 滚动事件监听函数 const handleScroll = () => { const scrollTop = window.document.body.scrollTop // scrollTop 为距离滚动条顶部高度 // scrollHeight 为整个文档高度 // 我们设定当滚动的距离大于 200 时,显示 【返回顶部】按钮 if (scrollTop > 200) { setVisibleBackTopBtn(true) } else { setVisibleBackTopBtn(false) } } // 点击按钮事件处理函数 const backToTopHandle = () => { // 把页面滚动到页面顶部 document.body.scrollTo({ left: 0, top: 0, behavior: 'smooth' }) } return ( <> { visibleBackTopBtn && <div id={styles.backToTop} onClick={backToTopHandle}></div> } </> ) }; export default BackToTopBtn;
添加节流,减少事件触发
我们知道,只要稍微滚动一下页面,就会触发页面的滚动事件。滚动事件的频繁触发,会带来性能问题。我们需要减少滚动事件的触发,来避免性能上的问题。因此,我们来实现一个节流函数:
/** * 节流 * @param {*} fn 将执行的函数 * @param {*} time 节流规定的时间 */ export const throttle = (fn, time) => { let timer = null return (...args) => { // 若timer === false,则执行,并在指定时间后将timer重制 if(!timer){ fn.apply(this, args) timer = setTimeout(() => { timer = null }, time) } } }
然后将我们的滚动事件监听函数使用节流函数进行包装:
const handleScroll = throttle(() => { const scrollTop = window.document.body.scrollTop if (scrollTop > 200) { setVisibleBackTopBtn(true) } else { setVisibleBackTopBtn(false) } }, 500)
如上面代码,在 500 毫秒内,滚动事件监听函数handleScroll最多只会执行一次,这样就减少了 handleScroll 的执行次数,从而在一定程度上解决了由于频繁触发滚动事件带来的性能问题。
完整代码
index.less
#backToTop { position: fixed; right: 20px; bottom: 10px; width: 42px; height: 42px; background-color: #1088e9; color: #ffffff; border-radius: 4px; font-size: 40px; cursor: pointer; text-align: center; &:before { content: '^'; display: block; font-family: serif, 'Times New Roman', Times; } &:hover:before { font-size: 12px; content: '回顶部'; line-height: 42px; } }
index.tsx
import React, { useEffect, useState } from 'react'; import { throttle } from './utils'; import styles from './index.less'; interface BackToTopBtnProps { } const BackToTopBtn: React.FC<BackToTopBtnProps> = props => { const [visibleBackTopBtn, setVisibleBackTopBtn] = useState(false) useEffect(() => { document.addEventListener('scroll', handleScroll, true) return () => document.removeEventListener('scroll', handleScroll) }, [visibleBackTopBtn]) const handleScroll = throttle(() => { const scrollTop = window.document.body.scrollTop // scrollTop为距离滚动条顶部高度 // scrollHeight为整个文档高度 if (scrollTop > 200) { setVisibleBackTopBtn(true) } else { setVisibleBackTopBtn(false) } }, 500) const backToTopHandle = () => { document.body.scrollTo( { left: 0, top: 0, behavior: 'smooth' } ) } return ( <> { visibleBackTopBtn && <div id={styles.backToTop} onClick={backToTopHandle}></div> } </> ) }; export default BackToTopBtn;
utils.ts
/** * 节流 * @param {*} fn 将执行的函数 * @param {*} time 节流规定的时间 */ export const throttle = (fn: Function, time: number): void => { let timer = null return (...args) => { // 若timer === false,则执行,并在指定时间后将timer重制 if(!timer){ fn.apply(this, args) timer = setTimeout(() => { timer = null }, time) } } }
最后彩蛋
在 css 中对 html 根元素添加 scroll-behavior: smooth;
属性,可以实现页面平滑滚动(不支持低版本的浏览器)。
html { scroll-behavior: smooth; }
在 【返回顶部】按钮的实现中,我们并没有在 html 根元素添加 scroll-behavior 属性,而是在 scrollTo 的参数里添加 behavior 属性来实现同样的效果。
Window.scrollTo()
语法
window.scrollTo(x-coord, y-coord)
window.scrollTo(options)
参数
x-coord
是文档中的横轴坐标。y-coord
是文档中的纵轴坐标。options
是一个包含三个属性的对象:
-
`
top
等同于
y-coord
`
-
left 等同于 `x
-coord
`
-
behavior
类型String,表示滚动行为,支持参数 smooth(平滑滚动),instant(瞬间滚动),默认值auto,实测效果等同于instant
例子
window.scrollTo( 0, 1000 ); // 设置滚动行为改为平滑的滚动 window.scrollTo({ top: 1000, behavior: "smooth" });