mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat: add scrollbar
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { on, off } from '@element-plus/utils/dom'
|
||||
import { renderThumbStyle, BAR_MAP } from './util'
|
||||
import { h } from 'vue'
|
||||
import { h, computed, ref, getCurrentInstance, onUnmounted, inject, Ref } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'Bar',
|
||||
@@ -11,83 +11,76 @@ export default {
|
||||
move: Number,
|
||||
},
|
||||
|
||||
computed: {
|
||||
bar() {
|
||||
return BAR_MAP[this.vertical ? 'vertical' : 'horizontal']
|
||||
},
|
||||
|
||||
wrap() {
|
||||
return this.$parent.wrap
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const { size, move, bar } = this
|
||||
|
||||
return h('div', {
|
||||
class: ['el-scrollbar__bar', 'is-' + bar.key],
|
||||
onMousedown: this.clickTrackHandler,
|
||||
}, h('div', {
|
||||
ref: 'thumb',
|
||||
class: 'el-scrollbar__thumb',
|
||||
onMousedown: this.clickThumbHandler,
|
||||
style: renderThumbStyle({
|
||||
size,
|
||||
move,
|
||||
bar,
|
||||
}),
|
||||
}))
|
||||
},
|
||||
|
||||
methods: {
|
||||
clickThumbHandler(e) {
|
||||
setup(props) {
|
||||
const instance = getCurrentInstance()
|
||||
const thumb = ref(null)
|
||||
const wrap = inject('scroll-bar-wrap', {}) as Ref
|
||||
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
|
||||
}
|
||||
this.startDrag(e)
|
||||
this[this.bar.axis] = (e.currentTarget[this.bar.offset] - (e[this.bar.client] - e.currentTarget.getBoundingClientRect()[this.bar.direction]))
|
||||
},
|
||||
startDrag(e)
|
||||
barStore.value[bar.value.axis] = (e.currentTarget[bar.value.offset] - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]))
|
||||
}
|
||||
|
||||
clickTrackHandler(e) {
|
||||
const offset = Math.abs(e.target.getBoundingClientRect()[this.bar.direction] - e[this.bar.client])
|
||||
const thumbHalf = (this.$refs.thumb[this.bar.offset] / 2)
|
||||
const thumbPositionPercentage = ((offset - thumbHalf) * 100 / this.$el[this.bar.offset])
|
||||
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])
|
||||
|
||||
this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100)
|
||||
},
|
||||
|
||||
startDrag(e) {
|
||||
wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize] / 100)
|
||||
}
|
||||
const startDrag = (e) =>{
|
||||
e.stopImmediatePropagation()
|
||||
this.cursorDown = true
|
||||
|
||||
on(document, 'mousemove', this.mouseMoveDocumentHandler)
|
||||
on(document, 'mouseup', this.mouseUpDocumentHandler)
|
||||
cursorDown.value = true
|
||||
on(document, 'mousemove', mouseMoveDocumentHandler)
|
||||
on(document, 'mouseup', mouseUpDocumentHandler)
|
||||
document.onselectstart = () => false
|
||||
},
|
||||
}
|
||||
|
||||
mouseMoveDocumentHandler(e) {
|
||||
if (this.cursorDown === false) return
|
||||
const prevPage = this[this.bar.axis]
|
||||
const mouseMoveDocumentHandler = (e) => {
|
||||
if (cursorDown.value === false) return
|
||||
const prevPage = barStore.value[bar.value.axis]
|
||||
|
||||
if (!prevPage) return
|
||||
|
||||
const offset = ((this.$el.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]) * -1)
|
||||
const thumbClickPosition = (this.$refs.thumb[this.bar.offset] - prevPage)
|
||||
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100 / this.$el[this.bar.offset])
|
||||
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)
|
||||
}
|
||||
|
||||
this.wrap[this.bar.scroll] = (thumbPositionPercentage * this.wrap[this.bar.scrollSize] / 100)
|
||||
},
|
||||
|
||||
mouseUpDocumentHandler() {
|
||||
this.cursorDown = false
|
||||
this[this.bar.axis] = 0
|
||||
off(document, 'mousemove', this.mouseMoveDocumentHandler)
|
||||
function mouseUpDocumentHandler(e) {
|
||||
cursorDown.value = false
|
||||
barStore.value[bar.value.axis] = 0
|
||||
off(document, 'mousemove', mouseMoveDocumentHandler)
|
||||
document.onselectstart = null
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
destroyed() {
|
||||
off(document, 'mouseup', this.mouseUpDocumentHandler)
|
||||
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,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { addResizeListener, removeResizeListener } from '@element-plus/utils/resize-event'
|
||||
import scrollbarWidth from '@element-plus/utils/scrollbar-width'
|
||||
import { toObject } from '@element-plus/utils/util'
|
||||
import { h } from 'vue'
|
||||
import { h, ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue'
|
||||
import Bar from './bar'
|
||||
|
||||
export default {
|
||||
@@ -23,98 +23,93 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
sizeWidth: '0',
|
||||
sizeHeight: '0',
|
||||
moveX: 0,
|
||||
moveY: 0,
|
||||
setup(props, ctx) {
|
||||
const sizeWidth = ref('0')
|
||||
const sizeHeight = ref('0')
|
||||
const moveX = ref(0)
|
||||
const moveY = ref(0)
|
||||
const wrap = ref(null)
|
||||
const resize = ref(null)
|
||||
|
||||
provide('scroll-bar-wrap', wrap)
|
||||
|
||||
const handleScroll = () => {
|
||||
moveY.value = ((wrap.value.scrollTop * 100) / wrap.value.clientHeight)
|
||||
moveX.value = ((wrap.value.scrollLeft * 100) / wrap.value.clientWidth)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
wrap() {
|
||||
return this.$refs.wrap
|
||||
},
|
||||
},
|
||||
const update = () => {
|
||||
if (!wrap.value) return
|
||||
|
||||
render() {
|
||||
const gutter = scrollbarWidth()
|
||||
let style = this.wrapStyle
|
||||
const heightPercentage = (wrap.value.clientHeight * 100 / wrap.value.scrollHeight)
|
||||
const widthPercentage = (wrap.value.clientWidth * 100 / wrap.value.scrollWidth)
|
||||
|
||||
if (gutter) {
|
||||
const gutterWith = `-${gutter}px`
|
||||
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`
|
||||
sizeHeight.value = (heightPercentage < 100) ? (heightPercentage + '%') : ''
|
||||
sizeWidth.value = (widthPercentage < 100) ? (widthPercentage + '%') : ''
|
||||
}
|
||||
|
||||
if (Array.isArray(this.wrapStyle)) {
|
||||
style = toObject(this.wrapStyle)
|
||||
style.marginRight = style.marginBottom = gutterWith
|
||||
} else if (typeof this.wrapStyle === 'string') {
|
||||
style += gutterStyle
|
||||
} else {
|
||||
style = gutterStyle
|
||||
onMounted(() => {
|
||||
if (props.native) return
|
||||
nextTick(update)
|
||||
!props.noresize && addResizeListener(resize.value, update)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (props.native) return
|
||||
!props.noresize && removeResizeListener(resize.value, update)
|
||||
})
|
||||
|
||||
return () => {
|
||||
const gutter = scrollbarWidth()
|
||||
let style = props.wrapStyle
|
||||
if (gutter) {
|
||||
const gutterWith = `-${gutter}px`
|
||||
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`
|
||||
|
||||
if (Array.isArray(props.wrapStyle)) {
|
||||
style = toObject(props.wrapStyle)
|
||||
style.marginRight = style.marginBottom = gutterWith
|
||||
} else if (typeof props.wrapStyle === 'string') {
|
||||
style += gutterStyle
|
||||
} else {
|
||||
style = gutterStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
const view = h(this.tag, {
|
||||
class: ['el-scrollbar__view', this.viewClass],
|
||||
style: this.viewStyle,
|
||||
ref: 'resize',
|
||||
}, this.$slots.default())
|
||||
const wrap = h('div', {
|
||||
ref: 'wrap',
|
||||
style: style,
|
||||
onScroll: this.handleScroll,
|
||||
class: [this.wrapClass, 'el-scrollbar__wrap', gutter ? '' : 'el-scrollbar__wrap--hidden-default'],
|
||||
}, [view])
|
||||
let nodes
|
||||
|
||||
if (!this.native) {
|
||||
nodes = [wrap, h(Bar, {
|
||||
move: this.moveX,
|
||||
size: this.sizeWidth,
|
||||
}), h(Bar, {
|
||||
vertical: true,
|
||||
move: this.moveY,
|
||||
size: this.sizeHeight,
|
||||
})]
|
||||
} else {
|
||||
nodes = h('div', {
|
||||
ref: 'wrap',
|
||||
class: [this.wrapClass, 'el-scrollbar__wrap'],
|
||||
style: style,
|
||||
const view = h(props.tag, {
|
||||
class: ['el-scrollbar__view', props.viewClass],
|
||||
style: props.viewStyle,
|
||||
ref: resize,
|
||||
}, ctx.slots.default())
|
||||
const _wrap = h('div', {
|
||||
ref: wrap,
|
||||
style,
|
||||
onScroll: handleScroll,
|
||||
class: [props.wrapClass, 'el-scrollbar__wrap', gutter ? '' : 'el-scrollbar__wrap--hidden-default'],
|
||||
}, [view])
|
||||
|
||||
let nodes
|
||||
|
||||
if (!props.native) {
|
||||
nodes = [_wrap,h(Bar,{
|
||||
move: moveX.value,
|
||||
size: sizeWidth.value,
|
||||
}),h(Bar,{
|
||||
vertical: true,
|
||||
move: moveY.value,
|
||||
size: sizeHeight.value,
|
||||
}),
|
||||
]
|
||||
} else {
|
||||
nodes = [
|
||||
h('div',{
|
||||
ref: wrap,
|
||||
class: [props.wrapClass, 'el-scrollbar__wrap'],
|
||||
style,
|
||||
}, [view]),
|
||||
]
|
||||
}
|
||||
|
||||
return h('div', { class: 'el-scrollbar' }, nodes)
|
||||
}
|
||||
return h('div', { class: 'el-scrollbar' }, nodes)
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleScroll() {
|
||||
const wrap = this.wrap
|
||||
|
||||
this.moveY = ((wrap.scrollTop * 100) / wrap.clientHeight)
|
||||
this.moveX = ((wrap.scrollLeft * 100) / wrap.clientWidth)
|
||||
},
|
||||
|
||||
update() {
|
||||
const wrap = this.wrap
|
||||
if (!wrap) return
|
||||
|
||||
const heightPercentage = (wrap.clientHeight * 100 / wrap.scrollHeight)
|
||||
const widthPercentage = (wrap.clientWidth * 100 / wrap.scrollWidth)
|
||||
|
||||
this.sizeHeight = (heightPercentage < 100) ? (heightPercentage + '%') : ''
|
||||
this.sizeWidth = (widthPercentage < 100) ? (widthPercentage + '%') : ''
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.native) return
|
||||
this.$nextTick(this.update)
|
||||
!this.noresize && addResizeListener(this.$refs.resize, this.update)
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.native) return
|
||||
!this.noresize && removeResizeListener(this.$refs.resize, this.update)
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user