从零搭建Vue3.0组件库之无限滚动组件
一.无限滚动组件注册方式
import { App } from 'vue'
import InfiniteScroll from './src/index'
(InfiniteScroll as any).install = (app: App): void => {
app.directive('InfiniteScroll', InfiniteScroll)
}
export default InfiniteScroll
1
2
3
4
5
6
2
3
4
5
6
二.滚动指令实现
1.指令定义
import { ComponentPublicInstance, ObjectDirective } from "vue";
type InfiniteScrollCallback = () => void;
type InfiniteScroll = HTMLElement & {
'infinite': {
container: HTMLElement // 滚动容器
delay: number // 延迟时间
cb: InfiniteScrollCallback // 触发的滚动方法
onScroll: () => void // 滚动时触发的回调
observer?: MutationObserver, // 用于监控高度不够时增加数据
instance:ComponentPublicInstance // 绑定到哪个组件实例
}
}
const InfiniteScroll: ObjectDirective<InfiniteScroll, InfiniteScrollCallback> = {
mounted(el,bindings){
const { value: cb, instance } = bindings;
await nextTick(); // 保证父元素加载完成
},
unmounted(el){}
}
export default InfiniteScroll;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2.获取指令参数
let { delay, immediate } = getScrollOptions(el, instance);
1
获取无限滚动信息
const attributes = {
delay: { // 节流延迟
type: Number,
default: 200
},
distance: { // 触底距离
type: Number,
default: 0
},
disabled: { // 是否禁用
type: Boolean,
default: false
},
immediate: { // 立即撑满内容
type: Boolean,
default: true
}
}
type Attrs = typeof attributes;
type ScrollOtions = { [K in keyof Attrs]: Attrs[K]['default'] }
const getScrollOptions = (el:HTMLElement, instance:ComponentPublicInstance):ScrollOtions => {
return Object.entries(attributes).reduce((memo, [name, option]) => {
const { type, default: defaultValue } = option;
const attrVal = el.getAttribute(`infinite-scroll-${name}`);
let value = instance[attrVal] ?? attrVal ?? defaultValue;
value = value == 'false' ? false : value;
value = type(value);
memo[name] = value;
return memo;
}, {} as ScrollOtions)
}
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
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
3.获取滚动容器
const container = getScrollContainer(el);
1
获取overflow 具备 scroll | auto的属性
const isScroll = (el: HTMLElement): RegExpMatchArray => {
let scrollY = getComputedStyle(el, '');
let overflow: string = scrollY['overflow-y'] || '';
return overflow.match(/(scroll|auto)/);
}
const getScrollContainer = (el: HTMLElement) => {
let parent = el;
while (parent) {
if (parent == document.documentElement) {
return document.documentElement
}
if (isScroll(parent)) {
return parent;
}
parent = parent.parentNode as HTMLElement;
}
return parent
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
4.定义scroll事件
const handleScroll = (el: InfiniteScroll, cb: InfiniteScrollCallback) => {
// 稍后实现滚动逻辑
}
const onScroll = throttle(() => handleScroll(el, cb), delay);
1
2
3
4
2
3
4
5.自动填充容器
if(immediate){
const observer = new MutationObserver(throttle(() => checkFull(el, cb), 100))
observer.observe(el, { childList: true, subtree: true });
el.infinite.observer = observer
checkFull(el, cb);
}
1
2
3
4
5
6
2
3
4
5
6
const checkFull = (el: InfiniteScroll, cb: InfiniteScrollCallback) => {
const { container ,instance} = el.infinite;
const { disabled } = getScrollOptions(el,instance);
if (disabled) return;
if (container.scrollHeight <= container.clientHeight) {
cb();
} else {
let ob = el.infinite.observer;
ob && ob.disconnect();
delete el.infinite.observer
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
6.滚动检测
container.addEventListener('scroll', onScroll)
1
const handleScroll = (el: InfiniteScroll, cb: InfiniteScrollCallback) => {
const {
container,
observer,
instance
} = el.infinite
const { disabled, distance } = getScrollOptions(el, instance);
const { clientHeight, scrollHeight, scrollTop } = container
let shouldTrigger = false;
if (observer || disabled) return;
if (container == el) {
shouldTrigger = scrollHeight - (clientHeight + scrollTop) <= distance
} else {
const { clientTop, scrollHeight: height } = el;
// 卷去的高度 + 可视区域 自己距离父亲的高度 + 自己的所有高度 - 距离
// 有可能定位 父距离顶部距离 - 自己离顶部的距离
shouldTrigger = scrollTop + clientHeight >= 0 + clientTop + height - distance
}
if (shouldTrigger) {
cb();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
7.指令卸载
const { container, onScroll } = el.infinite;
container.removeEventListener('scroll', onScroll);
1
2
2
移除监听事件