|
|
@@ -0,0 +1,121 @@
|
|
|
+<!-- 瀑布流展示-弹性布局方式 -->
|
|
|
+<template>
|
|
|
+ <div ref="waterfallRef" class="app-waterfall">
|
|
|
+ <template v-for="(data, i) in state.columns" :key="i">
|
|
|
+ <div class="app-waterfall__column" :style="i > 0 ? columnStyles : {}">
|
|
|
+ <template v-for="(item, n) in data" :key="n">
|
|
|
+ <div class="app-waterfall__column-item">
|
|
|
+ <slot :item="item">{{ item }}</slot>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { reactive, shallowRef, computed, watch, onMounted, onActivated, onDeactivated } from 'vue'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ //数据列表
|
|
|
+ dataList: {
|
|
|
+ type: Array,
|
|
|
+ default: () => ([])
|
|
|
+ },
|
|
|
+ //列数
|
|
|
+ column: {
|
|
|
+ type: Number,
|
|
|
+ default: 2
|
|
|
+ },
|
|
|
+ //间距
|
|
|
+ gap: {
|
|
|
+ type: Number,
|
|
|
+ default: 10
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const waterfallRef = shallowRef<HTMLDivElement>()
|
|
|
+
|
|
|
+const state = reactive({
|
|
|
+ columns: Array.from<unknown, unknown[]>({ length: props.column }, () => []),
|
|
|
+ renderedItemCount: 0,
|
|
|
+ loadedImageCount: 0,
|
|
|
+ isDeactivated: false
|
|
|
+})
|
|
|
+
|
|
|
+const columnStyles = computed(() => ({
|
|
|
+ marginLeft: `${props.gap}px`
|
|
|
+}))
|
|
|
+
|
|
|
+const render = async () => {
|
|
|
+ // 增量按需更新
|
|
|
+ if (props.dataList.length > state.renderedItemCount) {
|
|
|
+ const el = waterfallRef.value
|
|
|
+ if (el) {
|
|
|
+ const imageElements = el.querySelectorAll('img')
|
|
|
+ const images = Array.from(imageElements).slice(state.loadedImageCount)
|
|
|
+
|
|
|
+ if (images.length) {
|
|
|
+ // 等待所有图片加载完成
|
|
|
+ await Promise.all(images.map((img) => new Promise<void>((resolve) => {
|
|
|
+ if (img.complete) {
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ img.onload = () => resolve()
|
|
|
+ img.onerror = () => resolve()
|
|
|
+ }
|
|
|
+ })))
|
|
|
+
|
|
|
+ // 更新已加载完成的图片总数
|
|
|
+ state.loadedImageCount += images.length
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在 keepalive 组件下,如果数据还未全部加载完成之前跳转了页面,这会导致获取不到元素信息,从而影响布局的正常显示
|
|
|
+ // 所以需要暂停数据的加载,待页面重新显示后继续渲染
|
|
|
+ if (!state.isDeactivated) {
|
|
|
+ const columnElements = el.querySelectorAll<HTMLDivElement>('.app-waterfall__column')
|
|
|
+ const columnHeights = [...columnElements].map((e) => e.offsetHeight)
|
|
|
+
|
|
|
+ const minHeight = Math.min(...columnHeights) // 获取数组中最小值
|
|
|
+ const columnIndex = columnHeights.findIndex((e) => e === minHeight) // 最小值的索引位置
|
|
|
+
|
|
|
+ const data = state.columns[columnIndex]
|
|
|
+ const item = props.dataList[state.renderedItemCount]
|
|
|
+ data.push(item)
|
|
|
+
|
|
|
+ // 更新已渲染完成的数据总数
|
|
|
+ state.renderedItemCount++
|
|
|
+ requestAnimationFrame(() => render())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 待优化,未完成
|
|
|
+// watch(() => props.dataList, () => {
|
|
|
+// state.renderedItemCount = 0
|
|
|
+// state.loadedImageCount = 0
|
|
|
+// })
|
|
|
+
|
|
|
+watch(() => props.dataList.length, () => {
|
|
|
+ render()
|
|
|
+})
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ render()
|
|
|
+
|
|
|
+ onActivated(() => {
|
|
|
+ state.isDeactivated = false
|
|
|
+ // 对未完成加载的数据进行渲染
|
|
|
+ render()
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+onDeactivated(() => {
|
|
|
+ state.isDeactivated = true
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less">
|
|
|
+@import './index.less';
|
|
|
+</style>
|