背景
在最近的业务中,产品提了一个需求,在页面的右下角添加一个返回顶部的按钮。由于前端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.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop || 0
// 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.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop || 0
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"
});