[Element Plus 源码解析] Backtop 回到顶部

1,311 阅读1分钟

一、组件介绍

Backtop组件 官网链接

Backtop组件通常用于页面向下滚动后,提供快捷的返回顶部的按钮。

1.1 属性

  • target: string类型,滚动触发的对象,传入css元素选择器,默认是document;
  • visibility-height: number类型,滚动对象滚动高度达到此值时,展示Backtop组件;
  • right/bottom: number类型,控制显示位置,距离右侧/底部的距离。

1.2 事件

  • click: 点击组件时触发

二、源码分析

2.1 template

<template>
  // 官方过渡组件,提供过渡效果
  <transition name="el-fade-in">
    <div
      v-if="visible"
      :style="{
        'right': styleRight,
        'bottom': styleBottom
      }"
      class="el-backtop"
      @click.stop="handleClick"
    >
      // 默认插槽
      <slot>
        // 默认显示top icon
        <i class="el-icon-caret-top"></i>
      </slot>
    </div>
  </transition>
</template>

2.2 script

setup(props: IElBacktopProps, ctx) {
    const el = ref(null)
    const container = ref(null)
    const visible = ref(false)
    const styleBottom = computed(() => `${props.bottom}px`)
    const styleRight = computed(() => `${props.right}px`)
    const scope = 'ElBackTop'
    
    // 滚动到顶部
    const scrollToTop = () => {
      const beginTime = Date.now()
      const beginValue = el.value.scrollTop
      // 兼容浏览器不支持requestAnimationFrame的情况
      const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16))
      // 回调函数
      const frameFunc = () => {  
        const progress = (Date.now() - beginTime) / 500
        if (progress < 1) {
          // 不是一下子直接返回顶部,而是有一个向上的滚动过程
          el.value.scrollTop = beginValue * (1 - easeInOutCubic(progress))
          // 递归调用自身
          rAF(frameFunc)
        } else {
          // 500ms后 中断递归
          el.value.scrollTop = 0
        }
      }
      rAF(frameFunc)
    }
    
    // 容器滚动事件的处理函数
    const onScroll = () => {
      visible.value = el.value.scrollTop >= props.visibilityHeight
    }
    
    const handleClick = event => {
      scrollToTop()
      ctx.emit('click', event)
    }
    
    // 节流
    const throttledScrollHandler = throttle(onScroll, 300)

    onMounted(() => {
      // 默认滚动容器是document
      container.value = document
      el.value = document.documentElement
      // 指定容器的情况
      if (props.target) {
        el.value = document.querySelector(props.target)
        if (!el.value) {
          throwError(scope, `target is not existed: ${props.target}`)
        }
        container.value = el.value
      }
      // 滚动容器注册scroll事件
      on(container.value, 'scroll', throttledScrollHandler)
    })
    onBeforeUnmount(() => {
      // 注销scroll事件
      off(container.value, 'scroll', throttledScrollHandler)
    })

    return {
      el,
      container,
      visible,
      styleBottom,
      styleRight,
      handleClick,
    }
  }

2.3 总结

采用浏览器的requestAnimationFrame API,并在其回调函数中递归调用自身,达到向上滚动的动态效果,避免出现直接滚动到顶部的突兀感,在500ms后滚动至顶部。