refactor(components): refactor backtop (#3486)

This commit is contained in:
三咲智子
2021-09-19 15:10:59 +08:00
committed by GitHub
parent dd871cc8e9
commit b365fe6278
5 changed files with 124 additions and 125 deletions

View File

@@ -1,5 +1,6 @@
import { nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import Backtop from '../src/index.vue'
import Backtop from '../src/backtop.vue'
const _mount = (template: string) =>
mount(
@@ -21,6 +22,8 @@ describe('Backtop.vue', () => {
</div>
</div>
`)
await nextTick()
expect(wrapper.find('.el-backtop').exists()).toBe(false)
wrapper.element.scrollTop = 2000
await wrapper.trigger('scroll')

View File

@@ -1,13 +1,8 @@
import Backtop from './src/index.vue'
import type { App } from 'vue'
import { withInstall } from '@element-plus/utils/with-install'
import type { SFCWithInstall } from '@element-plus/utils/types'
import Backtop from './src/backtop.vue'
Backtop.install = (app: App): void => {
app.component(Backtop.name, Backtop)
}
export const ElBacktop = withInstall(Backtop)
export default ElBacktop
const _Backtop = Backtop as SFCWithInstall<typeof Backtop>
export default _Backtop
export const ElBacktop = _Backtop
export * from './src/backtop'

View File

@@ -0,0 +1,26 @@
import type { ExtractPropTypes } from 'vue'
export const backtopProps = {
visibilityHeight: {
type: Number,
default: 200,
},
target: {
type: String,
default: '',
},
right: {
type: Number,
default: 40,
},
bottom: {
type: Number,
default: 40,
},
} as const
export type BacktopProps = ExtractPropTypes<typeof backtopProps>
export const backtopEmits = {
click: (evt: MouseEvent) => evt instanceof MouseEvent,
}
export type BacktopEmits = typeof backtopEmits

View File

@@ -0,0 +1,89 @@
<template>
<transition name="el-fade-in">
<div
v-if="visible"
:style="{
right: styleRight,
bottom: styleBottom,
}"
class="el-backtop"
@click.stop="handleClick"
>
<slot>
<i class="el-icon-caret-top"></i>
</slot>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted, shallowRef } from 'vue'
import { useEventListener, useThrottleFn } from '@vueuse/core'
import { easeInOutCubic } from '@element-plus/utils/animation'
import { throwError } from '@element-plus/utils/error'
import { backtopEmits, backtopProps } from './backtop'
const COMPONENT_NAME = 'ElBacktop'
export default defineComponent({
name: COMPONENT_NAME,
props: backtopProps,
emits: backtopEmits,
setup(props, { emit }) {
const el = shallowRef<HTMLElement | undefined>(document.documentElement)
const container = shallowRef<Document | HTMLElement>(document)
const visible = ref(false)
const styleBottom = computed(() => `${props.bottom}px`)
const styleRight = computed(() => `${props.right}px`)
const scrollToTop = () => {
if (!el.value) return
const beginTime = Date.now()
const beginValue = el.value.scrollTop
const frameFunc = () => {
if (!el.value) return
const progress = (Date.now() - beginTime) / 500
if (progress < 1) {
el.value.scrollTop = beginValue * (1 - easeInOutCubic(progress))
requestAnimationFrame(frameFunc)
} else {
el.value.scrollTop = 0
}
}
requestAnimationFrame(frameFunc)
}
const handleScroll = () => {
if (el.value) visible.value = el.value.scrollTop >= props.visibilityHeight
}
const handleClick = (event: MouseEvent) => {
scrollToTop()
emit('click', event)
}
const handleScrollThrottled = useThrottleFn(handleScroll, 300)
onMounted(() => {
if (props.target) {
el.value =
document.querySelector<HTMLElement>(props.target) ?? undefined
if (!el.value) {
throwError(COMPONENT_NAME, `target is not existed: ${props.target}`)
}
container.value = el.value
}
useEventListener(container, 'scroll', handleScrollThrottled)
})
return {
visible,
styleBottom,
styleRight,
handleClick,
}
},
})
</script>

View File

@@ -1,114 +0,0 @@
<template>
<transition name="el-fade-in">
<div
v-if="visible"
:style="{
right: styleRight,
bottom: styleBottom,
}"
class="el-backtop"
@click.stop="handleClick"
>
<slot>
<i class="el-icon-caret-top"></i>
</slot>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import throttle from 'lodash/throttle'
import { on, off } from '@element-plus/utils/dom'
import { easeInOutCubic } from '@element-plus/utils/animation'
import { throwError } from '@element-plus/utils/error'
interface IElBacktopProps {
visibilityHeight: number
target: string
right: number
bottom: number
}
export default defineComponent({
name: 'ElBacktop',
props: {
visibilityHeight: {
type: Number,
default: 200,
},
target: {
type: String,
default: '',
},
right: {
type: Number,
default: 40,
},
bottom: {
type: Number,
default: 40,
},
},
emits: ['click'],
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
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 {
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(() => {
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
}
on(container.value, 'scroll', throttledScrollHandler)
})
onBeforeUnmount(() => {
off(container.value, 'scroll', throttledScrollHandler)
})
return {
el,
container,
visible,
styleBottom,
styleRight,
handleClick,
}
},
})
</script>