perf(scrollbar): when scrollbar isn't shown, hide the bar (#1342)

* perf(scrollbar): when scrollbar isn't shown, hide the bar

* fix: fix right click bar will return top

* perf: store and restore onselectstart

* perf: update
This commit is contained in:
kooriookami 2021-01-26 08:01:49 -06:00 committed by GitHub
parent 08421fab23
commit 903c05a848
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 156 additions and 124 deletions

View File

@ -1,86 +0,0 @@
import { defineComponent, h, computed, ref, getCurrentInstance, onUnmounted, inject, Ref } from 'vue'
import { on, off } from '@element-plus/utils/dom'
import { renderThumbStyle, BAR_MAP } from './util'
export default defineComponent({
name: 'Bar',
props: {
vertical: Boolean,
size: String,
move: Number,
},
setup(props) {
const instance = getCurrentInstance()
const thumb = ref(null)
const wrap = inject('scroll-bar-wrap', {} as Ref<Nullable<HTMLElement>>)
const bar = computed(() => {
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal']
})
const barStore = ref({})
const cursorDown = ref(null)
const clickThumbHandler= e => {
// prevent click event of right button
if (e.ctrlKey || e.button === 2) {
return
}
startDrag(e)
barStore.value[bar.value.axis] = (e.currentTarget[bar.value.offset] - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]))
}
const clickTrackHandler = e => {
const offset = Math.abs(e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client])
const thumbHalf = (thumb.value[bar.value.offset] / 2)
const thumbPositionPercentage = ((offset - thumbHalf) * 100 / instance.vnode.el[bar.value.offset])
wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize] / 100)
}
const startDrag = e => {
e.stopImmediatePropagation()
cursorDown.value = true
on(document, 'mousemove', mouseMoveDocumentHandler)
on(document, 'mouseup', mouseUpDocumentHandler)
document.onselectstart = () => false
}
const mouseMoveDocumentHandler = e => {
if (cursorDown.value === false) return
const prevPage = barStore.value[bar.value.axis]
if (!prevPage) return
const offset = ((instance.vnode.el.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * -1)
const thumbClickPosition = (thumb.value[bar.value.offset] - prevPage)
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100 / instance.vnode.el[bar.value.offset])
wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize] / 100)
}
function mouseUpDocumentHandler() {
cursorDown.value = false
barStore.value[bar.value.axis] = 0
off(document, 'mousemove', mouseMoveDocumentHandler)
document.onselectstart = null
}
onUnmounted(() => {
off(document, 'mouseup', mouseUpDocumentHandler)
})
return () => h('div', {
class: ['el-scrollbar__bar', 'is-' + bar.value.key],
onMousedown: clickTrackHandler,
},
h('div', {
ref: thumb,
class: 'el-scrollbar__thumb',
onMousedown: clickThumbHandler,
style: renderThumbStyle({
size: props.size,
move: props.move,
bar: bar.value,
}),
}),
)
},
})

View File

@ -0,0 +1,125 @@
<template>
<transition name="el-scrollbar-fade">
<div
v-show="visible"
ref="instance"
:class="['el-scrollbar__bar', 'is-' + bar.key]"
@mousedown="clickTrackHandler"
>
<div
ref="thumb"
class="el-scrollbar__thumb"
:style="thumbStyle"
@mousedown="clickThumbHandler"
></div>
</div>
</transition>
</template>
<script lang="ts">
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, ref, Ref } from 'vue'
import { off, on } from '@element-plus/utils/dom'
import { BAR_MAP, renderThumbStyle } from './util'
export default defineComponent({
name: 'Bar',
props: {
vertical: Boolean,
size: String,
move: Number,
},
setup(props) {
const instance = ref(null)
const thumb = ref(null)
const scrollbar = inject('scrollbar', {} as Ref<Nullable<HTMLElement>>)
const wrap = inject('scrollbar-wrap', {} as Ref<Nullable<HTMLElement>>)
const bar = computed(() => BAR_MAP[props.vertical ? 'vertical' : 'horizontal'])
const barStore = ref({})
const cursorDown = ref(null)
const visible = ref(false)
let onselectstartStore = null
const clickThumbHandler = e => {
// prevent click event of middle and right button
e.stopPropagation()
if (e.ctrlKey || [1, 2].includes(e.button)) {
return
}
startDrag(e)
barStore.value[bar.value.axis] = (e.currentTarget[bar.value.offset] - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]))
}
const clickTrackHandler = e => {
const offset = Math.abs(e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client])
const thumbHalf = (thumb.value[bar.value.offset] / 2)
const thumbPositionPercentage = ((offset - thumbHalf) * 100 / instance.value[bar.value.offset])
wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize] / 100)
}
const startDrag = e => {
e.stopImmediatePropagation()
cursorDown.value = true
on(document, 'mousemove', mouseMoveDocumentHandler)
on(document, 'mouseup', mouseUpDocumentHandler)
onselectstartStore = document.onselectstart
document.onselectstart = () => false
}
const mouseMoveDocumentHandler = e => {
if (cursorDown.value === false) return
const prevPage = barStore.value[bar.value.axis]
if (!prevPage) return
const offset = ((instance.value.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * -1)
const thumbClickPosition = (thumb.value[bar.value.offset] - prevPage)
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100 / instance.value[bar.value.offset])
wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize] / 100)
}
const mouseUpDocumentHandler = () => {
cursorDown.value = false
barStore.value[bar.value.axis] = 0
off(document, 'mousemove', mouseMoveDocumentHandler)
document.onselectstart = onselectstartStore
}
const thumbStyle = computed(() => renderThumbStyle({
size: props.size,
move: props.move,
bar: bar.value,
}))
const showBar = () => {
visible.value = !!props.size
}
const hideBar = () => {
visible.value = false
}
onMounted(() => {
on(scrollbar.value, 'mousemove', showBar)
on(scrollbar.value, 'mouseleave', hideBar)
})
onBeforeUnmount(() => {
off(document, 'mouseup', mouseUpDocumentHandler)
off(scrollbar.value, 'mousemove', showBar)
off(scrollbar.value, 'mouseleave', hideBar)
})
return {
instance,
thumb,
bar,
clickTrackHandler,
clickThumbHandler,
thumbStyle,
visible,
}
},
})
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="el-scrollbar">
<div ref="scrollbar" class="el-scrollbar">
<div
ref="wrap"
:class="[
@ -26,21 +26,10 @@
</div>
</template>
<script lang="ts">
import {
addResizeListener,
removeResizeListener,
} from '@element-plus/utils/resize-event'
import { addResizeListener, removeResizeListener } from '@element-plus/utils/resize-event'
import { toObject } from '@element-plus/utils/util'
import {
defineComponent,
ref,
onMounted,
onBeforeUnmount,
nextTick,
provide,
computed,
} from 'vue'
import Bar from './bar'
import { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, provide, ref } from 'vue'
import Bar from './bar.vue'
export default defineComponent({
name: 'ElScrollbar',
@ -77,10 +66,12 @@ export default defineComponent({
const sizeHeight = ref('0')
const moveX = ref(0)
const moveY = ref(0)
const scrollbar = ref(null)
const wrap = ref(null)
const resize = ref(null)
provide('scroll-bar-wrap', wrap)
provide('scrollbar', scrollbar)
provide('scrollbar-wrap', wrap)
const handleScroll = () => {
if (!props.native && wrap.value) {
@ -92,15 +83,20 @@ export default defineComponent({
const update = () => {
if (!wrap.value) return
const heightPercentage =
(wrap.value.clientHeight * 100) / wrap.value.scrollHeight
const widthPercentage =
(wrap.value.clientWidth * 100) / wrap.value.scrollWidth
const heightPercentage = (wrap.value.clientHeight * 100) / wrap.value.scrollHeight
const widthPercentage = (wrap.value.clientWidth * 100) / wrap.value.scrollWidth
sizeHeight.value = heightPercentage < 100 ? heightPercentage + '%' : ''
sizeWidth.value = widthPercentage < 100 ? widthPercentage + '%' : ''
}
const style = computed(() => {
if (Array.isArray(props.wrapStyle)) {
return toObject(props.wrapStyle)
}
return props.wrapStyle
})
onMounted(() => {
if (props.native) return
nextTick(update)
@ -111,19 +107,14 @@ export default defineComponent({
if (props.native) return
!props.noresize && removeResizeListener(resize.value, update)
})
const style = computed(() => {
let style = props.wrapStyle
if (Array.isArray(props.wrapStyle)) {
style = toObject(props.wrapStyle)
}
return style
})
return {
moveX,
moveY,
sizeWidth,
sizeHeight,
style,
scrollbar,
wrap,
resize,
update,

View File

@ -6,15 +6,6 @@
position: relative;
height: 100%;
&:hover,
&:active,
&:focus {
> .el-scrollbar__bar {
opacity: 1;
transition: opacity 340ms ease-out;
}
}
@include e(wrap) {
overflow: auto;
height: 100%;
@ -48,8 +39,6 @@
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
transition: opacity 120ms ease-out;
@include when(vertical) {
width: 6px;
@ -70,3 +59,16 @@
}
}
}
.el-scrollbar-fade-enter-active {
transition: opacity 340ms ease-out;
}
.el-scrollbar-fade-leave-active {
transition: opacity 120ms ease-out;
}
.el-scrollbar-fade-enter-from,
.el-scrollbar-fade-leave-active {
opacity: 0;
}