用 React Hooks 实现返回顶部按钮 ?

时间:2021-2-20 作者:admin

背景

在最近的业务中,产品提了一个需求,在页面的右下角添加一个返回顶部的按钮。由于前端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 是一个包含三个属性的对象:
  1. `

    top

    等同于

    y-coord

    `

  2. left 等同于 `x

    -coord

    `

  3. behavior 类型String,表示滚动行为,支持参数 smooth(平滑滚动),instant(瞬间滚动),默认值auto,实测效果等同于instant

例子

window.scrollTo( 0, 1000 );

// 设置滚动行为改为平滑的滚动
window.scrollTo({ 
    top: 1000, 
    behavior: "smooth" 
});
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。