跳至主要內容

BackTop 返回顶部

haneball大约 3 分钟Vue组件

概述

BackTop 是一个返回页面顶部的快捷按钮。

实现

  • props 传入的自定义按钮文本,默认为“顶部”
  • 使用 requestAnimationFrame() 替代直接赋值 scrollTop = 0,以获得更好的动画效果
<template>
  <div class="back-top-btn">
    <svg width="12" height="12">
      <polygon points="0,12 12,12 6,6" fill="#FFF" stroke="none"></polygon>
    </svg>
    <span>{{ text }}</span>
  </div>
</template>

<script>
export default {
  props: {
    text: {
      type: String,
      default: '顶部'
    }
  },
  data() {
    return {
      showBtn: false
    }
  },
  mounted() {
    // 监听滚动事件,控制是否显示按钮
    window.addEventListener('scroll', this.handleScroll)
  },
  beforeDestroy() {
    window.removeEventListener('scroll', this.handleScroll)
  },
  methods: {
    backTop() {
      let timer = null
      cancelAnimationFrame(timer)
      timer = requestAnimationFrame(function scrollAnime () {
        let distance = document.body.scrollTop || document.documentElement.scrollTop
        if (distance > 0) {    // 还未到达顶部
          // 以 100px 为单位递归向上滚动
          scrollTo(0, distance - 100)
          timer = requestAnimationFrame(scrollAnime)
        } else {    // 已到达顶部,取消动画
          cancelAnimationFrame(timer)
        }
      })
    },
    handleScroll() {
      if (document.documentElement.scrollTop >= 400) {
        this.showBtn = true
      } else {    // 卷起距离较短时,不显示按钮
        this.showBtn = false
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.back-top-btn {
  width: 48px;
  position: fixed;
  bottom: 120px;
  left: 120px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 8px 0;
  font-size: 12px;
  border: 2px solid #000;
  user-select: none;
}
</style>

优化

以上基本实现了 BackTop 按钮的功能,但存在可以改进的地方。

为了方便大部分人的操作,BackTop 按钮通常会固定在页面的右下角。以上例子直接在组件上使用了 fixed 定位,考虑到我们可能会给 BackTop 按钮设置更为复杂的样式,不妨尝试将其定位属性抽离,在外层的 div 完成定位。

BackTop 定位
BackTop 定位
  • back-top-outer:fixed 定位,覆盖整个页面,设置 pointer-events: none; 不触发事件,通过左右的 padding 实现水平定位
  • back-top-inner:relative 定位,用于填充 back-top-outer
  • back-top-wrap:absolute 定位,left: 100% 紧贴 back-top-inner,通过 bottom 实现垂直定位
  • back-top-btn:设置 pointer-events: auto; 可以触发事件

为了组件变得更美观,我们可以为 BackTop 按钮设置字体、背景、边框的样式,或是添加一个 fade 淡入淡出 动画。

最终代码如下。

<template>
  <div id="back-top-btn">
    <div v-show="showBtn" class="back-top-outer">
      <div class="back-top-inner">
        <div class="back-top-wrap">
          <div class="back-top-btn" @click="backTop">
            <svg width="12" height="12">
              <polygon points="0,12 12,12 6,6" fill="#FFF" stroke="none"></polygon>
            </svg>
            <span>{{ text }}</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    text: {
      type: String,
      default: '顶部'
    }
  },
  data() {
    return {
      showBtn: false
    }
  },
  mounted() {
    // 监听滚动事件,控制是否显示按钮
    window.addEventListener('scroll', this.handleScroll)
  },
  beforeDestroy() {
    window.removeEventListener('scroll', this.handleScroll)
  },
  methods: {
    backTop() {
      let timer = null
      cancelAnimationFrame(timer)
      timer = requestAnimationFrame(function scrollAnime () {
        let distance = document.body.scrollTop || document.documentElement.scrollTop
        if (distance > 0) {    // 还未到达顶部
          // 以 100px 为单位递归向上滚动
          scrollTo(0, distance - 100)
          timer = requestAnimationFrame(scrollAnime)
        } else {    // 已到达顶部,取消动画
          cancelAnimationFrame(timer)
        }
      })
    },
    handleScroll() {
      if (document.documentElement.scrollTop >= 400) {
        this.showBtn = true
      } else {    // 卷起距离较短时,不显示按钮
        this.showBtn = false
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.back-top-outer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 0 96px;
  background-color: transparent;
  z-index: 1999;
  pointer-events: none;
}

.back-top-inner {
  position: relative;
  width: 100%;
  height: 100%;
}

.back-top-wrap {
  position: absolute;
  bottom: 120px;
  left: 100%;
}

.back-top-btn {
  width: 48px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 8px 0;
  font-size: 12px;
  color: #FFF;
  background-color: #409EFF;
  pointer-events: auto;
  box-shadow: 0 2px 2px rgba(0, 0, 0, .08);
  border: none;
  border-radius: .5rem;
  user-select: none;
  animation: fade 0.2s linear forwards;
  &:hover {
    background-color: #66b1ff;
  }
  &:active {
    background-color: #3a8ee6;
    transform: scale(0.95);
  }
}

@keyframes fade {
  from { opacity: 0; }
  to { opacity: 100%; }
}
</style>