140 lines
3.1 KiB
Vue
140 lines
3.1 KiB
Vue
![]() |
<template>
|
||
|
<view class="VirtualizationList virtualization-list" @scroll.passive="update" >
|
||
|
<component
|
||
|
:is="listTag"
|
||
|
:class="listClass"
|
||
|
>
|
||
|
<template v-for="(info, i) in visibleItems" :key="info.item.$id">
|
||
|
<slot
|
||
|
:item="info.item"
|
||
|
:index="info.index"
|
||
|
:renderIndex="i"
|
||
|
:itemStyle="{ marginBottom: gap + 'px' }"
|
||
|
{{ info.item.text }}
|
||
|
></slot>
|
||
|
</template>
|
||
|
</component>
|
||
|
</view>
|
||
|
</template>
|
||
|
|
||
|
<script lang="ts">
|
||
|
import { defineComponent, PropType, nextTick } from "./vue"
|
||
|
import { obj } from "./types"
|
||
|
|
||
|
export default defineComponent({
|
||
|
props: {
|
||
|
items: { type: Array as PropType<obj[]>, default: () => [] },
|
||
|
enabled: { type: Boolean, default: true },
|
||
|
buffer: {
|
||
|
type: Number,
|
||
|
default: 200,
|
||
|
},
|
||
|
minItemHeight: { type: Number, default: 20 },
|
||
|
prerender: { type: Number, default: 20 },
|
||
|
listTag: { type: String, default: "view" },
|
||
|
listClass: { type: String },
|
||
|
itemClass: { type: String, default: "vl-item" },
|
||
|
gap: { type: Number, default: 0 },
|
||
|
afterCalcTop2: { type: Function as PropType<(top2: number) => number> },
|
||
|
isForceVisible: {
|
||
|
type: Function as PropType<(node: obj, index: number) => boolean>,
|
||
|
},
|
||
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
start: 0,
|
||
|
end: -1,
|
||
|
top: 0,
|
||
|
bottom: 0,
|
||
|
totalHeight: 0,
|
||
|
itemsHeight: <number[]>[],
|
||
|
mountedPromise: new Promise((resolve, reject) => {
|
||
|
this._mountedPromise_resolve = resolve;
|
||
|
}),
|
||
|
};
|
||
|
},
|
||
|
computed: {
|
||
|
visibleItems(): { item: obj; index: number }[] {
|
||
|
const r: { item: obj; index: number }[] = [];
|
||
|
console.log(this.items)
|
||
|
this.items.forEach((item: obj, index: number) => {
|
||
|
if (!this.enabled) {
|
||
|
r.push({ item, index });
|
||
|
} else if (
|
||
|
(index >= this.start && index <= this.end) ||
|
||
|
(this.isForceVisible && this.isForceVisible(item, index))
|
||
|
) {
|
||
|
r.push({ item, index });
|
||
|
}
|
||
|
});
|
||
|
console.log(r)
|
||
|
return r;
|
||
|
},
|
||
|
},
|
||
|
watch: {
|
||
|
enabled: {
|
||
|
immediate: true,
|
||
|
handler() {
|
||
|
if (!this.enabled) {
|
||
|
// @ts-ignore
|
||
|
this.totalHeight = undefined;
|
||
|
}
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
methods: {
|
||
|
getItemElHeight(el: UniApp.NodesRef) {
|
||
|
return new Promise((resolve, reject) => {
|
||
|
el.boundingClientRect(res => {
|
||
|
if(res) {
|
||
|
resolve(res)
|
||
|
} else {
|
||
|
reject(res)
|
||
|
}
|
||
|
}).exec()
|
||
|
})
|
||
|
},
|
||
|
update() {
|
||
|
const task = async () => {
|
||
|
this.start = 0;
|
||
|
this.end = this.items.length;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
created() {
|
||
|
this.bottom = this.prerender - 1;
|
||
|
},
|
||
|
mounted() {
|
||
|
// @ts-ignore
|
||
|
this._mountedPromise_resolve!(null);
|
||
|
let updatedOnce = false;
|
||
|
this.$watch(
|
||
|
() => [this.items],
|
||
|
() => {
|
||
|
this.itemsHeight = [];
|
||
|
nextTick(() => {
|
||
|
this.update();
|
||
|
if (!updatedOnce) {
|
||
|
this.update();
|
||
|
}
|
||
|
updatedOnce = true;
|
||
|
});
|
||
|
},
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => [this.buffer],
|
||
|
() => {
|
||
|
this.update();
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
})
|
||
|
</script>
|
||
|
|
||
|
<style lang="scss">
|
||
|
.vl-items {
|
||
|
overflow: hidden;
|
||
|
box-sizing: border-box;
|
||
|
}
|
||
|
</style>
|