/
ScrollBar.vue
245 lines (240 loc) · 11 KB
/
ScrollBar.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
* @Author: one-dragon
* @Date: 2018-11-14 10:55:51
* @Last Modified by: one-dragon
* @Last Modified time: 2018-12-28 10:28:42
*/
<template>
<el-scrollbar
ref="scrollbar"
:native="native"
:wrapStyle="wrapStyle"
:wrapClass="wrapClass"
:viewClass="viewClass"
:viewStyle="viewStyle"
:noresize="noresize"
:tag="tag"
:style="{ height: minheight ? minheight + 'px' : ''}">
<slot></slot>
</el-scrollbar>
</template>
<script>
// import ResizeObserver from 'element-ui/node_modules/resize-observer-polyfill';
import ResizeObserver from 'resize-observer-polyfill';
export default {
name: 'scrollbar',
props: {
// el-scrollbar的props
native: Boolean,
wrapStyle: {},
wrapClass: {},
viewClass: {},
viewStyle: {},
noresize: Boolean, // 如果 container 尺寸不会发生变化,最好设置它可以优化性能
tag: {
type: String,
default: 'div'
},
// 自定义的props
horizontalSlide: Boolean, // 是否启用鼠标滚轮滚动时,scrollbar的横向滑动滚动条进行滑动
minHeight: Number,
maxHeight: Number
},
data() {
return {
translateX: 0,
minheight: this.minHeight,
maxheight: this.maxHeight
}
},
methods: {
// 获取横向滚动条组件
getHorizontalBar() {
let horizontalBar = null;
this.$refs.scrollbar.$children.map((item) => {
if(item.$el.className == 'el-scrollbar__bar is-horizontal') {
horizontalBar = item;
}
})
return horizontalBar;
},
// 获取纵向滚动条组件
getVerticalBar() {
let vertical = null;
this.$refs.scrollbar.$children.map((item) => {
if(item.$el.className == 'el-scrollbar__bar is-vertical') {
vertical = item;
}
})
return vertical;
},
// 监听内容改变
resizeHandler(entries) {
for (let entry of entries) {
const listeners = entry.target.__resizeListeners__ || [];
if (listeners.length) {
listeners.forEach(fn => {
fn();
});
}
}
},
addResizeListener(element, fn) {
const isServer = typeof window === 'undefined';
if (isServer) return;
if (!element.__resizeListeners__) {
element.__resizeListeners__ = [];
element.__ro__ = new ResizeObserver(this.resizeHandler);
element.__ro__.observe(element);
}
element.__resizeListeners__.push(fn);
},
removeResizeListener(element, fn) {
if (!element || !element.__resizeListeners__) return;
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
element.__ro__.disconnect();
}
},
hasRootElement(ele) {
if(Array.from(ele).length > 1) {
throw new Error('ScrollBar component should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.');
}
return Array.from(ele)[0];
},
// 为了适配全局替换的ElSelect组件内调用scrollbar的方法
handleScroll() {
this.$refs.scrollbar && this.$refs.scrollbar.handleScroll();
}
},
mounted() {
let self = this;
// 判断是否传入maxheight,设置内容最大高度值
if(this.maxheight) {
this.$nextTick(() => {
let scrollbar = this.$refs.scrollbar;
let wrap = scrollbar.$el.querySelector('.el-scrollbar__wrap');
let contentElem = wrap.querySelectorAll('.el-scrollbar__view > *');
contentElem = this.hasRootElement(contentElem);
let vertical = this.getVerticalBar();
if(scrollbar.$el.style.height) {
this.minheight = Number(scrollbar.$el.style.height.replace('px', ''));
}
this.addResizeListener(contentElem, () => {
vertical.$el.style.visibility = 'hidden';
setTimeout(() => {
if(contentElem.offsetHeight < this.maxheight) { // 判断内容高度是否小于设置最大高度maxheight
if(contentElem.offsetHeight > scrollbar.$el.offsetHeight) {
scrollbar.$el.style.height = (contentElem.offsetHeight + 40) + 'px';
}
if(this.minheight && contentElem.offsetHeight < this.minheight) {
scrollbar.$el.style.height = this.minheight ? this.minheight + 'px' : '';
}else {
scrollbar.$el.style.height = (contentElem.offsetHeight + 40) + 'px';
}
}else if(contentElem.offsetHeight > this.maxheight) { // 内容高度是否大于设置最大高度maxheight
scrollbar.$el.style.height = this.maxheight + 'px';
}else {
scrollbar.$el.style.height = this.minheight ? this.minheight + 'px' : '';
}
vertical.$el.style.visibility = 'visible';
}, 0)
});
})
}
// 横向滚动条,滑轮滚动效果
if(!self.horizontalSlide) {
return;
}
this.$nextTick(() => {
// 获取el-scrollbar组件
let scrollbar = self.$refs.scrollbar;
// 获取wrap元素
let wrap = scrollbar.$el.querySelector('.el-scrollbar__wrap');
// 获取加载的内容元素(view里的内容)
let contentElem = wrap.querySelectorAll('.el-scrollbar__view > *');
// 判断ScrollBar是否只包含一个根元素,并返回当前元素
contentElem = self.hasRootElement(contentElem);
// 获取横向滚动条组件
let horizontalBar = self.getHorizontalBar();
// 监听加载的内容元素尺寸改变,如果改变调用el-scrollbar里的更新方法(update)加载出滚动条
self.addResizeListener(contentElem, scrollbar.update);
// 监听wrap中的滚轮滚动事件
let scrollFun = function(e) {
// 如果横向滚动条为0时,不需要进行计算横向滚动
if( horizontalBar.$refs.thumb.offsetWidth == 0) {
return;
}
e.stopImmediatePropagation();
// wrap元素的宽度
let wapW = wrap.clientWidth - 17;
// 横向滚动条的父级宽度
let barParentW = horizontalBar.$el.offsetWidth;
// 横向滚动条的子级宽度
let barChildW = horizontalBar.$refs.thumb.offsetWidth;
// 横向滚动条的子级translateX的最大百分比
let percent = (barParentW - barChildW) / barChildW * 100;
// 鼠标滚轮,滚动距离
let mouseClientY = e.clientY / 0.9;
// 防止用鼠标点击右键进行拖拽横向滚动条来改变translateX的值,每次判断上一次的translateX值,重新赋值给this.translateX
// console.log(horizontalBar.$refs.thumb.style.transform.replace('translateX(', '').replace('%)', ''));
let preTranslate = horizontalBar.$refs.thumb.style.transform.replace('translateX(', '').replace('%)', '');
self.translateX = preTranslate / 100 * wapW;
// 滚轮向下滚动(火狐下使用detail判断)
if(e.deltaY > 0 || e.detail > 0) {
// 判断计算横向滚动条,滑动最大距离
if(((self.translateX + mouseClientY) * 100) / wapW > percent) {
self.translateX = percent / 100 * wapW;
}else {
self.translateX += mouseClientY
}
}else { // 滚轮向上滚动
// 判断计算横向滚动条,滑动最小距离
if(self.translateX - mouseClientY < 0) {
self.translateX = 0;
}else {
self.translateX -= mouseClientY;
}
}
// 设置横向滚动条的子级移动百分比(改变css的translateX值)
scrollbar.moveX = ((self.translateX * 100) / wapW);
// horizontalBar.$refs.thumb.style.transform = `translateX(${(self.translateX * 100) / wapW}%)`;
// 设置wap元素的scrollLeft数值,来移动加载的内容元素
let num = (e.deltaY < 0 || e.detail < 0) ? 10 : 0; // 滚轮上滑时,减去一部分数值
let scrollLeft = (((self.translateX * 100) / wapW) / percent) * (contentElem.offsetWidth - wapW) - num;
wrap.scrollLeft = scrollLeft;
}
if(wrap.addEventListener){ // 火狐使用DOMMouseScroll监听滚轮事件
wrap.addEventListener('DOMMouseScroll', scrollFun, false);
}
wrap.onmousewheel = scrollFun;
})
}
}
</script>
<style lang="scss">
</style>
/*
/* istanbul ignore next */
const resizeHandler = function(entries) {
for (let entry of entries) {
const listeners = entry.target.__resizeListeners__ || [];
if (listeners.length) {
listeners.forEach(fn => {
fn();
});
}
}
};
const isServer = typeof window === 'undefined';
/* istanbul ignore next */
const addResizeListener = function(element, fn) {
if (isServer) return;
if (!element.__resizeListeners__) {
element.__resizeListeners__ = [];
element.__ro__ = new ResizeObserver(resizeHandler);
element.__ro__.observe(element);
}
element.__resizeListeners__.push(fn);
};
*/