feat(backtop): add backtop component

This commit is contained in:
Caaalabash 2020-08-04 10:54:43 +08:00 committed by jeremywu
parent df4fbb31b4
commit 548568abb0
8 changed files with 208 additions and 0 deletions

View File

@ -0,0 +1,29 @@
import { mount } from '@vue/test-utils'
import Backtop from '../src/index.vue'
const _mount = (template: string) => mount({
components: {
'el-backtop': Backtop,
},
template,
}, { attachTo: document.body })
describe('Backtop.vue', () => {
test('render',async () => {
const wrapper = _mount(`
<div class="target" style="height: 100px; overflow: auto">
<div style="height: 10000px; width: 100%">
<el-backtop target=".target" :visibilityHeight="2000" :right="100" :bottom="200" />
</div>
</div>
`)
expect(wrapper.find('.el-backtop').exists()).toBe(false)
wrapper.element.scrollTop = 2000
await wrapper.trigger('scroll')
expect(wrapper.find('.el-backtop').exists()).toBe(true)
expect(wrapper.find('.el-backtop').attributes('style')).toBe('right: 100px; bottom: 200px;')
expect(wrapper.find('.el-icon-caret-top').exists()).toBe(true)
await wrapper.trigger('click')
expect(wrapper.emitted()).toBeDefined()
})
})

View File

@ -0,0 +1,40 @@
<template>
<div v-if="visible" class="target" style="height:400px; overflow-y: auto">
<div style="height: 1000px; width: 100%">
<el-backtop target=".target">
<div
style="{
height: 100%;
width: 100%;
background-color: #f2f5f6;
box-shadow: 0 0 6px rgba(0,0,0, .12);
text-align: center;
line-height: 40px;
color: #1989fa;
}"
>
UP
</div>
</el-backtop>
<el-backtop target=".target" :bottom="100" />
</div>
</div>
</template>
<script>
import { onMounted, defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const visible = ref(false)
onMounted(() => {
visible.value = true
})
return {
visible,
}
},
})
</script>

View File

@ -0,0 +1,5 @@
export { default as BasicUsage } from './basic.vue'
export default {
title: 'Backtop',
}

View File

@ -0,0 +1,5 @@
import { App } from 'vue'
import Backtop from './src/index.vue'
export default (app: App): void => {
app.component(Backtop.name, Backtop)
}

View File

@ -0,0 +1,12 @@
{
"name": "@element-plus/backtop",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.1"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.0"
}
}

View File

@ -0,0 +1,113 @@
<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-es'
interface IElBacktopProps {
visibilityHeight: number
target: string
right: number
bottom: number
}
const cubic = value => Math.pow(value, 3)
const easeInOutCubic = value => value < 0.5 ? cubic(value * 2) / 2 : 1 - cubic((1 - value) * 2) / 2
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) {
let el, container
const visible = ref(false)
const styleBottom = computed(() => `${props.bottom}px`)
const styleRight = computed(() => `${props.right}px`)
const init = () => {
container = document
el = document.documentElement
if (props.target) {
el = document.querySelector(props.target)
if (!el) {
throw new Error(`target is not existed: ${props.target}`)
}
container = el
}
}
const scrollToTop = () => {
const beginTime = Date.now()
const beginValue = el.scrollTop
const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16))
const frameFunc = () => {
const progress = (Date.now() - beginTime) / 500
if (progress < 1) {
el.scrollTop = beginValue * (1 - easeInOutCubic(progress))
rAF(frameFunc)
} else {
el.scrollTop = 0
}
}
rAF(frameFunc)
}
const onScroll = () => {
const scrollTop = el.scrollTop
visible.value = scrollTop >= props.visibilityHeight
}
const handleClick = (event) => {
scrollToTop()
ctx.emit('click', event)
}
const throttledScrollHandler = throttle(onScroll, 300)
onMounted(() => {
init()
container.addEventListener('scroll', throttledScrollHandler)
})
onBeforeUnmount(() => {
container.removeEventListener('scroll', throttledScrollHandler)
})
return {
visible,
styleBottom,
styleRight,
handleClick,
}
},
})
</script>

View File

@ -1,5 +1,6 @@
import type { App } from 'vue'
import ElAvatar from '@element-plus/avatar'
import ElBacktop from '@element-plus/backtop'
import ElButton from '@element-plus/button'
import ElBadge from '@element-plus/badge'
import ElCard from '@element-plus/card'
@ -18,6 +19,7 @@ import ElNotification from '@element-plus/notification'
export {
ElAvatar,
ElBacktop,
ElLayout,
ElButton,
ElBadge,
@ -37,6 +39,7 @@ export {
export default function install(app: App): void {
ElAvatar(app)
ElBacktop(app)
ElButton(app)
ElBadge(app)
ElCard(app)

View File

@ -16,6 +16,7 @@
"dependencies": {
"@element-plus/avatar": "^0.0.0",
"@element-plus/badge": "^0.0.0",
"@element-plus/backtop": "^0.0.0",
"@element-plus/button": "^0.0.0",
"@element-plus/breadcrumb": "^0.0.0",
"@element-plus/card": "^0.0.0",