back-top.ts 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import { onBeforeUnmount, onMounted, ref } from 'vue'
  2. import { isClient } from '@vueuse/core'
  3. import { throttleAndDebounce } from '../utils'
  4. const threshold = 960
  5. const cubic = (value: number): number => value ** 3
  6. const easeInOutCubic = (value: number): number => (value < 0.5 ? cubic(value * 2) / 2 : 1 - cubic((1 - value) * 2) / 2)
  7. export const useBackTop = (offset = 200) => {
  8. const shouldShow = ref(false)
  9. const throttleResize = throttleAndDebounce(onResize, 300)
  10. const throttleScroll = throttleAndDebounce(onScroll, 160)
  11. onMounted(() => {
  12. if (!isClient) return
  13. onResize()
  14. onScroll()
  15. window.addEventListener('resize', throttleResize)
  16. })
  17. onBeforeUnmount(() => {
  18. if (!isClient) return
  19. window.removeEventListener('resize', throttleResize)
  20. window.removeEventListener('scroll', throttleScroll)
  21. })
  22. const scrollToTop = () => {
  23. const beginTime = Date.now()
  24. const beginValue = document.documentElement.scrollTop
  25. const rAF = window.requestAnimationFrame
  26. const frameFunc = () => {
  27. const progress = (Date.now() - beginTime) / 500
  28. if (progress < 1) {
  29. document.documentElement.scrollTop = beginValue * (1 - easeInOutCubic(progress))
  30. rAF(frameFunc)
  31. } else {
  32. document.documentElement.scrollTop = 0
  33. }
  34. }
  35. rAF(frameFunc)
  36. }
  37. function onResize() {
  38. if (!isClient) return
  39. const { clientWidth } = document.body
  40. if (clientWidth < threshold) {
  41. window.addEventListener('scroll', throttleScroll)
  42. } else {
  43. window.removeEventListener('scroll', throttleScroll)
  44. }
  45. }
  46. function onScroll() {
  47. if (!isClient) return
  48. shouldShow.value = document.documentElement.scrollTop > offset
  49. }
  50. return {
  51. shouldShow,
  52. scrollToTop,
  53. }
  54. }