perf: 优化瀑布流组件

This commit is contained in:
kailong321200875 2023-11-05 09:07:15 +08:00
parent 3477173b76
commit 82eb7f16ad
1 changed files with 115 additions and 38 deletions

View File

@ -20,10 +20,13 @@ const prop = defineProps({
src: 'src', src: 'src',
height: 'height' height: 'height'
}), }),
cols: propTypes.number.def(undefined),
loadingText: propTypes.string.def('加载中...'), loadingText: propTypes.string.def('加载中...'),
loading: propTypes.bool.def(false), loading: propTypes.bool.def(false),
end: propTypes.bool.def(false), end: propTypes.bool.def(false),
endText: propTypes.string.def('没有更多了') endText: propTypes.string.def('没有更多了'),
autoCenter: propTypes.bool.def(true),
layout: propTypes.oneOf(['javascript', 'flex']).def('flex')
}) })
const wrapEl = ref<HTMLDivElement>() const wrapEl = ref<HTMLDivElement>()
@ -37,7 +40,7 @@ const wrapWidth = ref(0)
const loadMore = ref<HTMLDivElement>() const loadMore = ref<HTMLDivElement>()
// = / // = /
const cols = ref(0) const innerCols = ref(0)
const filterData = ref<any[]>([]) const filterData = ref<any[]>([])
@ -49,11 +52,11 @@ const filterWaterfall = async () => {
const container = unref(wrapEl) as HTMLElement const container = unref(wrapEl) as HTMLElement
if (!container) return if (!container) return
cols.value = Math.floor(container.clientWidth / (width + gap)) innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
const length = data.length const length = data.length
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
if (i < unref(cols)) { if (i < unref(innerCols)) {
heights.value[i] = data[i][props.height as string] heights.value[i] = data[i][props.height as string]
filterData.value.push({ filterData.value.push({
...data[i], ...data[i],
@ -66,7 +69,7 @@ const filterWaterfall = async () => {
let minHeight = heights.value[0] let minHeight = heights.value[0]
let index = 0 let index = 0
// //
for (let j = 1; j < unref(cols); j++) { for (let j = 1; j < unref(innerCols); j++) {
if (unref(heights)[j] < minHeight) { if (unref(heights)[j] < minHeight) {
minHeight = unref(heights)[j] minHeight = unref(heights)[j]
index = j index = j
@ -83,14 +86,42 @@ const filterWaterfall = async () => {
} }
} }
wrapHeight.value = Math.max(...unref(heights)) wrapHeight.value = Math.max(...unref(heights))
wrapWidth.value = unref(cols) * (width + gap) - gap wrapWidth.value = unref(innerCols) * (width + gap) - gap
}
const flexWaterfall = async () => {
const { width, gap } = prop
const data = prop.data as any[]
await nextTick()
const container = unref(wrapEl) as HTMLElement
if (!container) return
innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
const length = data.length
//
const arr = new Array(unref(innerCols)).fill([])
// dataarr
for (let i = 0; i < length; i++) {
const index = i % unref(innerCols)
arr[index] = [...arr[index], data[i]]
}
filterData.value = arr
}
const initLayout = () => {
const { layout } = prop
if (layout === 'javascript') {
filterWaterfall()
} else if (layout === 'flex') {
flexWaterfall()
}
} }
watch( watch(
() => prop.data, () => [prop.data, prop.cols],
async () => { () => {
await nextTick() initLayout()
filterWaterfall()
}, },
{ {
immediate: true immediate: true
@ -99,7 +130,7 @@ watch(
onMounted(() => { onMounted(() => {
if (unref(prop.reset)) { if (unref(prop.reset)) {
useEventListener(window, 'resize', debounce(filterWaterfall, 300)) useEventListener(window, 'resize', debounce(initLayout, 300))
} }
useIntersectionObserver( useIntersectionObserver(
unref(loadMore), unref(loadMore),
@ -117,22 +148,29 @@ onMounted(() => {
<template> <template>
<div <div
:class="[prefixCls, 'flex', 'justify-center', 'items-center']" :class="[
prefixCls,
'flex',
'items-center',
{
'justify-center': autoCenter
}
]"
ref="wrapEl" ref="wrapEl"
:style="{ :style="{
height: `${wrapHeight + 40}px` height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
}"
>
<div
class="relative"
:style="{
width: `${wrapWidth}px`,
height: `${wrapHeight + 40}px`
}" }"
> >
<template v-if="layout === 'javascript'">
<div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
<div <div
v-for="(item, $index) in filterData" v-for="(item, $index) in filterData"
:class="[`${prefixCls}-item__${$index}`, 'absolute']" :class="[
`${prefixCls}-item__${$index}`,
{
absolute: layout === 'javascript'
}
]"
:key="`water-${$index}`" :key="`water-${$index}`"
:style="{ :style="{
width: `${width}px`, width: `${width}px`,
@ -153,5 +191,44 @@ onMounted(() => {
{{ end ? endText : loadingText }} {{ end ? endText : loadingText }}
</div> </div>
</div> </div>
</template>
<template v-else-if="layout === 'flex'">
<div
class="relative flex pb-40px"
:style="{
width: cols ? '100%' : 'auto'
}"
>
<div
v-for="(item, $index) in filterData"
:key="`waterWrap-${$index}`"
class="flex-1"
:style="{
marginRight: $index === filterData.length - 1 ? '0' : `${gap}px`
}"
>
<div
v-for="(child, i) in item"
:key="`waterWrap-${$index}-${i}`"
:style="{
marginBottom: `${gap}px`,
width: cols ? '100%' : `${width}px`,
height: cols ? 'auto' : `${child[props.height as string]}px`
}"
>
<img :src="child[props.src as string]" class="w-full h-full block" alt="" srcset="" />
</div>
</div>
<div
ref="loadMore"
class="h-40px flex justify-center absolute w-full items-center"
:style="{
bottom: 0
}"
>
{{ end ? endText : loadingText }}
</div>
</div>
</template>
</div> </div>
</template> </template>