|
|
@@ -1,6 +1,6 @@
|
|
|
<template>
|
|
|
<div class="app-waterfall">
|
|
|
- <ul ref="warterfallRef" v-if="dataList.length">
|
|
|
+ <ul ref="waterfallRef" v-if="dataList.length">
|
|
|
<li v-for="(item, index) in dataList" :key="index">
|
|
|
<slot :item="item">{{ item }}</slot>
|
|
|
</li>
|
|
|
@@ -9,7 +9,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
-import { shallowRef, watch, nextTick, onActivated, onMounted } from 'vue'
|
|
|
+import { shallowReactive, shallowRef, computed, watch, nextTick, onActivated, onMounted } from 'vue'
|
|
|
|
|
|
const props = defineProps({
|
|
|
//数据列表
|
|
|
@@ -29,19 +29,20 @@ const props = defineProps({
|
|
|
}
|
|
|
})
|
|
|
|
|
|
-const warterfallRef = shallowRef<HTMLDivElement>()
|
|
|
+const waterfallRef = shallowRef<HTMLDivElement>()
|
|
|
|
|
|
-const state: { total: number; hightList: number[]; } = {
|
|
|
+const state = shallowReactive({
|
|
|
+ isRendering: false,
|
|
|
total: 0,
|
|
|
- hightList: [] //瀑布流高度列表
|
|
|
-}
|
|
|
+ heightList: [] as number[] //瀑布流高度列表
|
|
|
+})
|
|
|
|
|
|
const render = () => {
|
|
|
- state.total = 0
|
|
|
- state.hightList = []
|
|
|
+ if (state.isRendering) return
|
|
|
+ state.isRendering = true
|
|
|
|
|
|
nextTick(async () => {
|
|
|
- const el = warterfallRef.value
|
|
|
+ const el = waterfallRef.value
|
|
|
if (el) {
|
|
|
const nodes = el.querySelectorAll('li')
|
|
|
|
|
|
@@ -50,22 +51,18 @@ const render = () => {
|
|
|
const images = li.querySelectorAll('img')
|
|
|
|
|
|
// 等待所有图片加载完成
|
|
|
- for (let n = 0; n < images.length; n++) {
|
|
|
- await new Promise<void>((resolve) => {
|
|
|
- if (images[n].complete) {
|
|
|
- resolve()
|
|
|
- } else {
|
|
|
- images[n].onload = () => resolve()
|
|
|
- images[n].onerror = () => resolve()
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- if (!el.offsetWidth) {
|
|
|
- // 在 keepalive 组件下,如果在数据未全部加载出来之前跳转了页面,这会导致元素获取不到宽度,从而影响瀑布流布局的正常显示
|
|
|
- // 所以需要停止还未加载完成的数据渲染,待页面重新打开后继续加载
|
|
|
- break
|
|
|
- }
|
|
|
+ await Promise.all([...images].map((img) => new Promise<void>((resolve) => {
|
|
|
+ if (img.complete) {
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ img.onload = () => resolve()
|
|
|
+ img.onerror = () => resolve()
|
|
|
+ }
|
|
|
+ })))
|
|
|
+
|
|
|
+ // 在 keepalive 组件下,如果数据还未全部加载完成之前跳转了页面,这会导致元素获取不到宽度,从而影响布局的正常显示
|
|
|
+ // 所以需要暂停数据的加载,待页面重新显示后继续渲染
|
|
|
+ if (!el.offsetWidth) break
|
|
|
|
|
|
const space = (props.column - 1) * props.gap // 总间距
|
|
|
const width = (el.offsetWidth - space) / props.column // 每列的宽度
|
|
|
@@ -77,33 +74,43 @@ const render = () => {
|
|
|
if (i < props.column) {
|
|
|
li.style.top = '0'
|
|
|
li.style.left = (width * i) + (props.gap * i) + 'px'
|
|
|
- state.hightList.push(li.offsetHeight + props.gap)
|
|
|
+ state.heightList.push(li.offsetHeight + props.gap)
|
|
|
} else {
|
|
|
- const minHeight = Math.min(...state.hightList) // 获取数组中最小值
|
|
|
- const index = state.hightList.findIndex((e) => e === minHeight) // 最小值的索引位置
|
|
|
+ const minHeight = Math.min(...state.heightList) // 获取数组中最小值
|
|
|
+ const index = state.heightList.findIndex((e) => e === minHeight) // 最小值的索引位置
|
|
|
li.style.top = minHeight + 'px'
|
|
|
li.style.left = (width * index) + (props.gap * index) + 'px'
|
|
|
- state.hightList[index] += li.offsetHeight + props.gap
|
|
|
+ state.heightList[index] += li.offsetHeight + props.gap
|
|
|
}
|
|
|
|
|
|
state.total++
|
|
|
}
|
|
|
|
|
|
- const maxHeight = Math.max(...state.hightList); // 获取数组中最大值
|
|
|
+ const maxHeight = Math.max(...state.heightList); // 获取数组中最大值
|
|
|
el.style.height = (maxHeight - props.gap) + 'px'
|
|
|
}
|
|
|
+
|
|
|
+ state.isRendering = false
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-watch(() => props.dataList, () => render())
|
|
|
+// 渲染标志位
|
|
|
+const shouldRender = computed(() => props.dataList.length > state.total)
|
|
|
+
|
|
|
+watch(() => props.dataList, () => {
|
|
|
+ state.total = 0
|
|
|
+ state.heightList = []
|
|
|
+ render()
|
|
|
+})
|
|
|
|
|
|
onMounted(() => {
|
|
|
render()
|
|
|
|
|
|
onActivated(() => {
|
|
|
// 对未完成加载的数据进行渲染
|
|
|
- if (props.dataList.length > state.total) {
|
|
|
- render()
|
|
|
+ if (shouldRender.value) {
|
|
|
+ // 延迟回调,如果 dataList 数据变更,会优先触发 watch 函数
|
|
|
+ nextTick(() => render())
|
|
|
}
|
|
|
})
|
|
|
})
|