-
Notifications
You must be signed in to change notification settings - Fork 710
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
69ec5f0
commit d543e56
Showing
3 changed files
with
131 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,156 @@ | ||
<script lang="ts" setup> | ||
import { propTypes } from '@/utils/propTypes' | ||
import { useDesign } from '@/hooks/web/useDesign' | ||
import { ref, nextTick, unref, onMounted } from 'vue' | ||
import { isString } from '@/utils/is' | ||
import { ref, nextTick, unref, onMounted, watch } from 'vue' | ||
import { useEventListener, useIntersectionObserver } from '@vueuse/core' | ||
import { debounce } from 'lodash-es' | ||
const { getPrefixCls } = useDesign() | ||
const prefixCls = getPrefixCls('waterfall') | ||
const emit = defineEmits(['loadMore']) | ||
const prop = defineProps({ | ||
data: propTypes.arrayOf(propTypes.any), | ||
reset: propTypes.bool.def(false), | ||
reset: propTypes.bool.def(true), | ||
width: propTypes.number.def(200), | ||
height: propTypes.number.def(0), | ||
gap: propTypes.number.def(20), | ||
getContainer: propTypes.func.def(() => document.body), | ||
props: propTypes.objectOf(propTypes.string).def({ | ||
src: 'src', | ||
height: 'height' | ||
}) | ||
}), | ||
loadingText: propTypes.string.def('加载中...'), | ||
loading: propTypes.bool.def(false), | ||
end: propTypes.bool.def(false), | ||
endText: propTypes.string.def('没有更多了') | ||
}) | ||
const wrapEl = ref<HTMLDivElement>() | ||
const heights = ref<number[]>([]) | ||
const wrapHeight = ref(0) | ||
const wrapWidth = ref(0) | ||
const loadMore = ref<HTMLDivElement>() | ||
// 首先确定列数 = 页面宽度 / 图片宽度 | ||
const cols = ref(0) | ||
const filterData = ref<any[]>([]) | ||
const filterWaterfall = async () => { | ||
const { props, width, gap, getContainer, height } = prop | ||
const { props, width, gap } = prop | ||
const data = prop.data as any[] | ||
await nextTick() | ||
const container = (getContainer?.() || unref(wrapEl)) as HTMLElement | ||
const container = unref(wrapEl) as HTMLElement | ||
if (!container) return | ||
cols.value = Math.floor(container.clientWidth / (width + gap)) | ||
const length = data.length | ||
for (let i = 0; i < length; i++) { | ||
if (i + 1 < unref(cols)) { | ||
if (height || data[i][props.height as string]) { | ||
// 如果有全局高度,就使用全局高度 | ||
// 如果 data[i][props.height as string] 是字符串,只保留数字字符串 | ||
const itemHeight = isString(data[i][props.height as string]) | ||
? Number(data[i][props.height as string].replace(/[^0-9]/gi, '')) | ||
: data[i][props.height as string] | ||
heights.value[i] = height || itemHeight | ||
} else { | ||
// 说明在第一列 | ||
const itemEl = container.querySelector(`.${prefixCls}-item__${i}`) | ||
itemEl?.addEventListener('load', () => { | ||
const clientRect = itemEl?.getBoundingClientRect() | ||
console.log(clientRect) | ||
}) | ||
// const imgEl = new Image() | ||
// imgEl.src = data[i][props.src as string] | ||
// imgEl.onload = async () => { | ||
// // const itemEl = container.querySelector(`.${prefixCls}-item__${i}`) | ||
// const clientRect = itemEl?.getBoundingClientRect() | ||
// if (clientRect) { | ||
// heights.value[i] = clientRect?.height | ||
// } | ||
// } | ||
if (i < unref(cols)) { | ||
heights.value[i] = data[i][props.height as string] | ||
filterData.value.push({ | ||
...data[i], | ||
top: 0, | ||
left: i * (width + gap) | ||
}) | ||
} else { | ||
// 其他行,先找出最矮的那一列 和 索引 | ||
// 假设最小高度是第一个元素 | ||
let minHeight = heights.value[0] | ||
let index = 0 | ||
// 找出最小高度 | ||
for (let j = 1; j < cols.value; j++) { | ||
if (unref(heights)[j] < minHeight) { | ||
minHeight = unref(heights)[j] | ||
index = j | ||
} | ||
} | ||
// 更新最矮高度 | ||
heights.value[index] += data[i][props.height as string] + gap | ||
filterData.value.push({ | ||
...data[i], | ||
top: minHeight + gap, | ||
left: index * (width + gap) | ||
}) | ||
} | ||
} | ||
wrapHeight.value = Math.max(...unref(heights)) | ||
wrapWidth.value = unref(cols) * (width + gap) - gap | ||
} | ||
watch( | ||
() => prop.data, | ||
async () => { | ||
await nextTick() | ||
filterWaterfall() | ||
}, | ||
{ | ||
immediate: true | ||
} | ||
) | ||
onMounted(() => { | ||
filterWaterfall() | ||
if (unref(prop.reset)) { | ||
useEventListener(window, 'resize', debounce(filterWaterfall, 300)) | ||
} | ||
useIntersectionObserver( | ||
unref(loadMore), | ||
([{ isIntersecting }]) => { | ||
if (isIntersecting && !prop.loading && !prop.end) { | ||
emit('loadMore') | ||
} | ||
}, | ||
{ | ||
threshold: 0.1 | ||
} | ||
) | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div :class="prefixCls" ref="wrapEl"> | ||
<div | ||
:class="[prefixCls, 'flex', 'justify-center', 'items-center']" | ||
ref="wrapEl" | ||
:style="{ | ||
height: `${wrapHeight + 40}px` | ||
}" | ||
> | ||
<div | ||
v-for="(item, $index) in data" | ||
:class="`${prefixCls}-item__${$index}`" | ||
:key="`water-${$index}`" | ||
class="relative" | ||
:style="{ | ||
width: `${width}px` | ||
width: `${wrapWidth}px`, | ||
height: `${wrapHeight + 40}px` | ||
}" | ||
> | ||
<img :src="item[props.src as string]" class="w-full block" alt="" srcset="" /> | ||
<div | ||
v-for="(item, $index) in filterData" | ||
:class="[`${prefixCls}-item__${$index}`, 'absolute']" | ||
:key="`water-${$index}`" | ||
:style="{ | ||
width: `${width}px`, | ||
height: `${item[props.height as string]}px`, | ||
top: `${item.top}px`, | ||
left: `${item.left}px` | ||
}" | ||
> | ||
<img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" /> | ||
</div> | ||
<div | ||
ref="loadMore" | ||
class="h-40px flex justify-center absolute w-full" | ||
:style="{ | ||
top: `${wrapHeight + gap}px` | ||
}" | ||
> | ||
{{ end ? endText : loadingText }} | ||
</div> | ||
</div> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters