|
|
@@ -1,18 +1,92 @@
|
|
|
<template>
|
|
|
<div class="app-waterfall">
|
|
|
- <div class="app-waterfall-item" v-for="(item, index) in dataList" :key="index">
|
|
|
- <slot :item="item"></slot>
|
|
|
- </div>
|
|
|
+ <ul ref="warterfallRef">
|
|
|
+ <li v-for="(item, index) in dataList" :key="index">
|
|
|
+ <slot :item="item"></slot>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
-defineProps({
|
|
|
+import { shallowRef, watch, nextTick } from 'vue'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ //数据列表
|
|
|
dataList: {
|
|
|
type: Array,
|
|
|
default: () => ([])
|
|
|
+ },
|
|
|
+ //列数
|
|
|
+ column: {
|
|
|
+ type: Number,
|
|
|
+ default: 2
|
|
|
+ },
|
|
|
+ //间距
|
|
|
+ spacing: {
|
|
|
+ type: Number,
|
|
|
+ default: 10
|
|
|
}
|
|
|
})
|
|
|
+
|
|
|
+const warterfallRef = shallowRef<HTMLDivElement>()
|
|
|
+const total = shallowRef(0)
|
|
|
+const hightList: number[] = [] //瀑布流高度列表
|
|
|
+
|
|
|
+const render = () => {
|
|
|
+ nextTick(async () => {
|
|
|
+ const el = warterfallRef.value
|
|
|
+ if (el) {
|
|
|
+ const nodes = el.querySelectorAll('li')
|
|
|
+
|
|
|
+ for (let i = total.value; i < nodes.length; i++) {
|
|
|
+ const li = nodes[i]
|
|
|
+ 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()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const space = (props.column - 1) * props.spacing // 总间距
|
|
|
+ const width = (el.offsetWidth - space) / props.column // 每列的宽度
|
|
|
+
|
|
|
+ li.style.width = width + 'px'
|
|
|
+ li.style.opacity = '1'
|
|
|
+
|
|
|
+ //判断是否首行
|
|
|
+ if (i < props.column) {
|
|
|
+ li.style.top = '0'
|
|
|
+ li.style.left = (width * i) + (props.spacing * i) + 'px'
|
|
|
+ hightList.push(li.offsetHeight + props.spacing)
|
|
|
+ } else {
|
|
|
+ const minHeight = Math.min(...hightList) // 获取数组中最小值
|
|
|
+ const index = hightList.findIndex((e) => e === minHeight) // 最小值的索引位置
|
|
|
+ li.style.top = minHeight + 'px'
|
|
|
+ li.style.left = (width * index) + (props.spacing * index) + 'px'
|
|
|
+ hightList[index] += li.offsetHeight + props.spacing
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const maxHeight = Math.max(...hightList); // 获取数组中最大值
|
|
|
+ el.style.height = maxHeight + 'px'
|
|
|
+ total.value = nodes.length
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+watch(() => props.dataList, () => render())
|
|
|
</script>
|
|
|
|
|
|
<style lang="less">
|