Skip to content

Commit 30eb7b2

Browse files
committed
添加scrollbar
1 parent 551351e commit 30eb7b2

File tree

7 files changed

+295
-4
lines changed

7 files changed

+295
-4
lines changed

packages/global.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface anyObject<T = any> {
2+
[prop: string]: T
3+
}

packages/index.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@ import { ElementUIOptions } from './component'
2020
import icon from './icon'
2121
import input from './input'
2222
import main from './main'
23+
import scrollbar from './scrollbar'
2324
import tag from './tag'
2425

25-
interface anyObject<T = any> {
26-
[prop: string]: T
27-
}
26+
import { anyObject } from 'packages/global'
2827

2928
export const Alert = alert
3029
export const Aside = aside
@@ -42,6 +41,7 @@ export const CollapseTransition = collapseTransition
4241
export const Icon = icon
4342
export const Input = input
4443
export const Main = main
44+
export const Scrollbar = scrollbar
4545
export const Tag = tag
4646

4747
export const components = [
@@ -61,6 +61,7 @@ export const components = [
6161
Icon,
6262
Input,
6363
Main,
64+
Scrollbar,
6465
Tag
6566
]
6667

@@ -101,5 +102,6 @@ export default {
101102
Icon,
102103
Input,
103104
Main,
105+
Scrollbar,
104106
Tag
105107
}

packages/scrollbar/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { App } from 'vue'
2+
import Component from './src/index.vue'
3+
export const install = function (app: App) {
4+
app.component(Component.name as string, Component)
5+
}
6+
7+
export default Component

packages/scrollbar/src/bar.vue

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<script lang="ts">
2+
import { defineComponent, ref, reactive, h, getCurrentInstance, ComponentInternalInstance, PropType, onMounted, nextTick, onBeforeUnmount, computed } from 'vue'
3+
// @ts-ignore
4+
import { on, off } from '@/utils/dom'
5+
import { renderThumbStyle, BAR_MAP, BaseMap } from './util'
6+
import { EventType } from 'mitt'
7+
8+
export default defineComponent({
9+
name: 'Bar',
10+
11+
props: {
12+
vertical: Boolean,
13+
size: String,
14+
move: Number
15+
},
16+
17+
setup(props) {
18+
const state = reactive({
19+
X: 0,
20+
Y: 0,
21+
cursorDown: false
22+
})
23+
const bar = computed(() => {
24+
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal'] as BaseMap
25+
})
26+
const instance = getCurrentInstance() as ComponentInternalInstance
27+
const wrap = instance.parent?.refs.wrap as HTMLElement
28+
const thumb = instance.refs.thumb as HTMLElement
29+
const clickTrackHandler = (e: MouseEvent) => {
30+
const target = e.target as HTMLElement
31+
32+
const { direction, offset, client, scroll, scrollSize} = bar.value
33+
const el = instance.vnode?.el as HTMLElement
34+
const newOffset = Math.abs(target.getBoundingClientRect()[direction] - e[client])
35+
const thumbHalf = (thumb[offset] / 2)
36+
const thumbPositionPercentage = ((newOffset - thumbHalf) * 100 / el[offset])
37+
38+
wrap[scroll] = (thumbPositionPercentage * wrap[scrollSize] / 100)
39+
}
40+
41+
const clickThumbHandler = (e: MouseEvent) => {
42+
// prevent click event of right button
43+
if (e.ctrlKey || e.button === 2) {
44+
return
45+
}
46+
startDrag(e)
47+
const { axis, direction, offset, client } = bar.value
48+
const currentTarget = e.currentTarget as HTMLElement
49+
state[axis] = (currentTarget[offset] - (e[client] - currentTarget.getBoundingClientRect()[direction]))
50+
}
51+
52+
const startDrag = (e: Event) => {
53+
e.stopImmediatePropagation()
54+
state.cursorDown = true
55+
56+
on(document, 'mousemove', mouseMoveDocumentHandler)
57+
on(document, 'mouseup', mouseUpDocumentHandler)
58+
document.onselectstart = () => false
59+
}
60+
61+
const mouseMoveDocumentHandler = (e: MouseEvent) => {
62+
if (state.cursorDown === false) return
63+
const { axis, offset, scroll, direction, client, scrollSize } = bar.value
64+
const prevPage = state[axis]
65+
66+
if (!prevPage) return
67+
const el = instance.vnode.el as HTMLElement
68+
69+
const newOffset = ((el.getBoundingClientRect()[direction] - e[client]) * -1)
70+
const thumbClickPosition = (thumb[offset] - prevPage)
71+
const thumbPositionPercentage = ((newOffset - thumbClickPosition) * 100 / el[offset])
72+
73+
wrap[scroll] = (thumbPositionPercentage * wrap[scrollSize] / 100)
74+
}
75+
76+
const mouseUpDocumentHandler = (e: Event) => {
77+
state.cursorDown = false
78+
state[bar.value.axis] = 0
79+
off(document, 'mousemove', mouseMoveDocumentHandler)
80+
document.onselectstart = null
81+
}
82+
83+
return () => {
84+
const { size, move } = props
85+
return h('div', {
86+
class: ['el-scrollbar__bar', 'is-' + bar.value.key],
87+
onMousedown: clickTrackHandler
88+
}, [
89+
h('div', {
90+
ref: 'thumb',
91+
class: 'el-scrollbar__thumb',
92+
style: renderThumbStyle({ size, move, bar: bar.value }),
93+
onMousedown: clickThumbHandler
94+
})
95+
])
96+
}
97+
},
98+
99+
// destroyed() {
100+
// off(document, 'mouseup', this.mouseUpDocumentHandler)
101+
// }
102+
})
103+
</script>

packages/scrollbar/src/index.vue

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<script lang="ts">
2+
// reference https://github.com/noeldelgado/gemini-scrollbar/blob/master/index.js
3+
import { defineComponent, ref, reactive, h, getCurrentInstance, ComponentInternalInstance, PropType, onMounted, nextTick, onBeforeUnmount } from 'vue'
4+
// @ts-ignore
5+
import { addResizeListener, removeResizeListener } from '@/utils/resize-event'
6+
// @ts-ignore
7+
import scrollbarWidth from '@/utils/scrollbar-width'
8+
// @ts-ignore
9+
import { toObject } from '@/utils/util'
10+
import Bar from './bar.vue'
11+
import { anyObject } from 'packages/global'
12+
13+
/* istanbul ignore next */
14+
export default defineComponent({
15+
name: 'ElScrollbar',
16+
17+
components: { Bar },
18+
19+
props: {
20+
native: Boolean,
21+
wrapStyle: {
22+
type: [Object, Array, String] as PropType<anyObject | (anyObject|String)[] | String>,
23+
default: ''
24+
},
25+
wrapClass: {},
26+
viewClass: {},
27+
viewStyle: {},
28+
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
29+
tag: {
30+
type: String,
31+
default: 'div'
32+
}
33+
},
34+
35+
setup(props, cxt) {
36+
const state = reactive({
37+
sizeWidth: '0',
38+
sizeHeight: '0',
39+
moveX: 0,
40+
moveY: 0
41+
})
42+
const instance = getCurrentInstance() as ComponentInternalInstance
43+
const wrap = ref(instance.refs.wrap as HTMLElement)
44+
const handleScroll = () => {
45+
state.moveY = ((wrap.value.scrollTop * 100) / wrap.value.clientHeight)
46+
state.moveX = ((wrap.value.scrollLeft * 100) / wrap.value.clientWidth)
47+
}
48+
const update = () => {
49+
let heightPercentage, widthPercentage
50+
if (!wrap.value) return
51+
52+
heightPercentage = (wrap.value.clientHeight * 100 / wrap.value.scrollHeight)
53+
widthPercentage = (wrap.value.clientWidth * 100 / wrap.value.scrollWidth)
54+
55+
state.sizeHeight = (heightPercentage < 100) ? (heightPercentage + '%') : ''
56+
state.sizeWidth = (widthPercentage < 100) ? (widthPercentage + '%') : ''
57+
}
58+
onMounted(() => {
59+
if (props.native) return
60+
nextTick(update)
61+
!props.noresize && addResizeListener(instance.refs.resize, update)
62+
})
63+
onBeforeUnmount(() => {
64+
if (props.native) return
65+
!props.noresize && removeResizeListener(instance.refs.resize, update)
66+
})
67+
return () => {
68+
const gutter = scrollbarWidth()
69+
let style = props.wrapStyle
70+
if (gutter) {
71+
const gutterWith = `-${gutter}px`
72+
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`
73+
74+
if (Array.isArray(props.wrapStyle)) {
75+
style = toObject(props.wrapStyle) as anyObject
76+
style.marginRight = style.marginBottom = gutterWith
77+
} else if (typeof props.wrapStyle === 'string') {
78+
style += gutterStyle
79+
} else {
80+
style = gutterStyle
81+
}
82+
}
83+
84+
const view = h(props.tag, {
85+
ref: 'resize',
86+
class: ['el-scrollbar__view', props.viewClass],
87+
style: props.viewStyle,
88+
}, cxt.slots.default?.() || [])
89+
const wrap = h('div', {
90+
ref: 'wrap',
91+
style,
92+
class: [props.wrapClass, 'el-scrollbar__wrap', gutter ? '' : 'el-scrollbar__wrap--hidden-default'],
93+
onScroll: handleScroll,
94+
}, [view])
95+
96+
let nodes
97+
98+
if (!props.native) {
99+
nodes = ([
100+
wrap,
101+
h('Bar', {
102+
move: state.moveX,
103+
size: state.sizeWidth
104+
}),
105+
h('Bar', {
106+
vertical: '',
107+
move: state.moveY,
108+
size: state.sizeHeight
109+
})
110+
])
111+
} else {
112+
nodes = [h('div', {
113+
ref: 'wrap',
114+
style,
115+
class: [props.wrapClass, 'el-scrollbar__wrap']
116+
}, [view])]
117+
}
118+
return h('div', { class: 'el-scrollbar' }, nodes)
119+
}
120+
}
121+
})
122+
123+
</script>>

packages/scrollbar/src/util.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { anyObject } from 'packages/global'
2+
export interface BaseMap {
3+
offset: 'offsetHeight' | 'offsetWidth'
4+
scroll: 'scrollTop' | 'scrollLeft'
5+
scrollSize: 'scrollHeight' | 'scrollWidth'
6+
size: 'height' | 'width'
7+
key: 'vertical' | 'horizontal'
8+
axis: 'Y' | 'X'
9+
client: 'clientY' | 'clientX'
10+
direction: 'top' | 'left'
11+
}
12+
export interface BarMap {
13+
vertical: BaseMap
14+
horizontal: BaseMap
15+
}
16+
export const BAR_MAP: BarMap = {
17+
vertical: {
18+
offset: 'offsetHeight',
19+
scroll: 'scrollTop',
20+
scrollSize: 'scrollHeight',
21+
size: 'height',
22+
key: 'vertical',
23+
axis: 'Y',
24+
client: 'clientY',
25+
direction: 'top'
26+
},
27+
horizontal: {
28+
offset: 'offsetWidth',
29+
scroll: 'scrollLeft',
30+
scrollSize: 'scrollWidth',
31+
size: 'width',
32+
key: 'horizontal',
33+
axis: 'X',
34+
client: 'clientX',
35+
direction: 'left'
36+
}
37+
}
38+
39+
export function renderThumbStyle({ move, size, bar }: {
40+
move?: Number,
41+
size?: string,
42+
bar: anyObject
43+
}) {
44+
const style: anyObject = {}
45+
const translate = `translate${bar.axis}(${ move }%)`
46+
47+
style[bar.size] = size
48+
style.transform = translate
49+
style.msTransform = translate
50+
style.webkitTransform = translate
51+
52+
return style
53+
}

src/utils/scrollbar-width.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Vue from 'vue';
33
let scrollBarWidth;
44

55
export default function() {
6-
if (Vue.prototype.$isServer) return 0;
6+
// if (Vue.prototype.$isServer) return 0;
77
if (scrollBarWidth !== undefined) return scrollBarWidth;
88

99
const outer = document.createElement('div');

0 commit comments

Comments
 (0)