文章目录
使用
<kl-waterfall
@touchBottom="touchBottom"
:distant="50"
:cope="4"
:margin="10"
:sleep="200"
>
<kl-waterfall-item v-for="(item, index) in state.imgs" :key="index" border>
<div class="item">
<img :src="item.url" alt="" />
<p>
{{ item.content }}
</p>
</div>
</kl-waterfall-item>
</kl-waterfall>
实现
waterfall index文件
<template>
<div class="kl-waterfall" ref="waterRef">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import {
onMounted,
reactive,
ref,
onBeforeUpdate,
onBeforeUnmount,
} from "vue-demi";
import { throttle } from "../../utils/tool";
const props = defineProps({
cope: {
// 份数
type: Number,
default: 5,
required: false,
},
margin: {
// 每份的间隔
type: Number,
default: 10,
required: false,
},
sleep: {
// 默认延时加载 建议图片越多,值越大
type: Number,
default: 200,
required: false,
},
distant: {
// 距离多远触底 就触发 触底回调
type: Number,
default: 200,
required: false,
},
});
const emits = defineEmits(["touchBottom"]);
const waterRef = ref();
interface i_state {
childWidth: number; // 每项的公共宽度
heightArr: number[]; // 存储每列的高度数组
max: number; // 当前的最高项的高度
top: number; // 当前kl-waterfall 距离顶部的距离
clientHeight: number; // 可视区高度
isBottom: boolean; // 是否到了底部
lastLength: number; // 上次的长度
timer: any; // 记录timer
}
const state = reactive<i_state>({
childWidth: 0,
heightArr: [],
max: 0,
top: 0,
clientHeight: 0,
isBottom: false,
lastLength: 0,
timer: null,
});
// 滚动事件
const handleScroll = throttle(() => {
//scrollTop就是触发滚轮事件时滚轮的高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// console.log(scrollTop + state.clientHeight);
if (
state.max + state.top <= scrollTop + state.clientHeight + props.distant &&
!state.isBottom
) {
state.isBottom = true;
emits("touchBottom", {
code: 200,
msg: "触底了",
});
}
}, 50);
onMounted(() => {
const width = waterRef.value.clientWidth;
state.top = waterRef.value.offsetTop;
state.clientHeight = document.documentElement.clientHeight;
// 每个子元素的宽度
let childWidth = Math.ceil(
(width - (props.cope + 1) * props.margin) / props.cope
);
state.childWidth = childWidth;
// 开始处理业务
handleEvent();
// 添加触底事件
window.addEventListener("scroll", handleScroll, true);
});
onBeforeUnmount(() => {
clearTimeout(state.timer);
state.timer = null;
});
// 具体处理相关的业务
function handleEvent() {
// console.log(state.lastLength);
clearTimeout(state.timer);
state.timer = null;
state.timer = setTimeout(() => {
// 给每个子项设宽
let childs = waterRef.value.children;
// console.log(childs.length);
let childLength = childs.length;
for (let i = state.lastLength; i < childLength; i++) {
childs[i].style.width = state.childWidth + "px";
}
state.isBottom = false;
// 获取每个元素的高
for (let i = state.lastLength; i < childLength; i++) {
let height: number = Number(childs[i].offsetHeight) || 0;
// console.log(height);
// 第一排设置
if (i < props.cope) {
childs[i].style.top = props.margin + "px";
childs[i].style.left =
(i + 1) * props.margin + i * state.childWidth + "px";
state.heightArr.push(props.margin + height);
// console.log("kkk");
} else {
// 找到最小项
let minHeight = Math.min(...state.heightArr);
// 找到最小项的index
let minHeightIndex = state.heightArr.findIndex((item) => {
return item == minHeight;
});
childs[i].style.top =
state.heightArr[minHeightIndex] + props.margin + "px";
childs[i].style.left =
(minHeightIndex + 1) * props.margin +
minHeightIndex * state.childWidth +
"px";
state.heightArr[minHeightIndex] = minHeight + props.margin + height;
}
}
// 重新给父元素定高度
let max = Math.max(...state.heightArr);
state.max = max;
waterRef.value.style.height = max + props.margin + "px";
// 记录下当前的长度
state.lastLength = childLength;
}, props.sleep);
}
// 当数据更新时的业务
onBeforeUpdate(() => {
handleEvent();
});
</script>
<style lang="scss" scoped>
.kl-waterfall {
display: block;
position: relative;
}
.kl-loading {
height: 50px;
text-align: center;
color: #666;
}
</style>
kl-waterfall-item
<template>
<div
:class="[
'kl-clearFix',
'kl-waterfall-item',
border ? 'kl-waterfall-item-border' : '',
]"
>
<slot />
</div>
</template>
<script lang="ts" setup>
defineProps({
border: {
type: Boolean,
default: false,
required: false,
},
});
</script>
<style lang="scss" scoped>
.kl-waterfall-item-border {
box-shadow: 0 0 5px #555;
display: block;
position: absolute;
}
</style>