<template>
<div ref="main" class="main" id="main" :style="{ height: maxH + 'px' }">
<!-- <div v-if="loading" class="loading">Loading...</div> -->
<div
class="item"
:class="[moveMode, styleArr[i] && styleArr[i].showClass]"
v-for="(item, i) in list"
:key="i"
:style="styleArr[i]"
:ref="'item' + i"
>
<slot
:state="(styleArr[i] && styleArr[i].state) || 'loading'"
:data="item"
:index="i"
></slot>
</div>
<iframe v-if="autoResize" ref="autoExpand" class="autoExpand"></iframe>
</div>
</template>
<script>
let loaderCache = {};
let loaderImg = new Map();
let time = null;
export default {
props: {
list: {
type: Array,
default: () => [],
},
imgKey: {
type: String,
default: "src",
},
// 列数
col: {
type: Number,
default: 0,
},
// 列宽,和列数只能生效其一,列数优先
colWidth: {
type: Number,
default: 0,
},
// 是否根据容器宽度变化重新计算
autoResize: {
type: Boolean,
default: true,
},
// 是否填充满容器
fillBox: {
type: Boolean,
default: false,
},
// 移动模式transform、convention
moveMode: {
type: String,
default: "transform",
},
// 自动重绘时的过渡时间
moveTransitionDuration: {
type: Number,
default: 0.4,
},
// 元素之间的间隔
gap: {
type: Number,
default: 10,
},
},
data() {
return {
styleArr: [], //数据对应的样式
colW: 0, //列宽
maxH: 599, //最高的列
mainW: 0, //容器宽度
_col: 0, //列缓存
__col: 0, //内部维护列数
batchCB: null, //批处理Promise
onRender: null, //渲染完毕回调函数
loading: false,
};
},
computed: {
//窗口改变节流函数
resizeDebounce() {
return this.isTransition ? this.debounce(this.resize, 100) : this.resize;
},
//默认过渡的条件规则
isTransition() {
return this.autoResize && this.col < 1;
},
},
mounted() {
this.mainW = this.getWidth();
this.init();
this.polling();
},
watch: {
["list.length"]: {
deep: false,
async handler(newV, oldV) {
if (newV > oldV) {
this.loading = true; // 设置loading状态为true
await this.nextTick();
this.batchCB = this.initItem(oldV);
}
},
},
autoResize: {
handler(newV) {
newV &&
setTimeout(() => {
this.refs.autoExpand.contentWindow.onresize = (e) => {
this.resizeDebounce();
};
});
},
immediate: true,
},
},
methods: {
async repaints(start = 0, duration) {
await this.nextTick();
this.mainW = this.getWidth();
this.calcCol();
this.calcXY(start, "repaints", duration);
},
init(start = 0) {
this.calcCol();
this.batchCB = this.initItem(start);
},
getWidth() {
return (this.refs.main.getBoundingClientRect() || {}).width || 0;
},
async resize(start = 0) {
if (!this.refs.main) return;
let width = this.getWidth();
if (width == this.mainW) return;
this.mainW = width;
this.calcCol();
if (this.autoResize) {
if (this.fillBox || this.col || this.__col != this._col) {
this.calcXY(start, "resize");
}
}
},
calcCol() {
let col = 3;
if (this.col) {
col = this.col;
this.colW = (this.mainW - this.gap * (col - 1)) / col; // 修改列宽计算,考虑间隔
} else if (this.colWidth) {
col = parseInt(this.mainW / (this.colWidth + this.gap)) || 1;
if (this.mainW % (this.colWidth + this.gap) && this.fillBox) {
this.colW = (this.mainW - this.gap * (col - 1)) / col;
} else {
this.colW = this.colWidth;
}
} else {
this.colW = (this.mainW - this.gap * (col - 1)) / col;
}
this.__col = col;
return col;
},
polling() {
clearInterval(time);
time = setInterval(() => {
for (let item of loaderImg) {
let key = item[0];
item = item[1];
if (item.img.height>0 || item.img.complete) {
item.cb(item.img);
loaderImg.delete(key);
} else {
return;
}
}
}, 300);
},
initItem(start = 0) {
let list = this.list.slice(start);
let loadNum = 0;
list.forEach((e, i) => {
let _i = i + start;
if (!this.styleArr[_i]) {
this.styleArr[_i] = {};
}
this.styleArr[_i].width = this.colW + "px";
this.loader(
e[this.imgKey],
() => {
loadNum++;
this.styleArr[_i].complete = true;
this.styleArr[_i].state = "complete";
this.set(this.styleArr, _i, this.styleArr[_i]);
if (loadNum != list.length) return;
this.waitRender(start);
},
this.getColDom(_i).getElementsByClassName("waterfall-img")[0],
i
);
});
},
waitRender(start) {
for (var i = 0; i < this.styleArr.length; i++) {
if (i < start) {
if (!this.styleArr[i] || !this.styleArr[i].complete) return;
}
}
this.calcXY(start);
},
async calcXY(index = 0, cause = "data", duration) {
duration = isNaN(duration) ? this.moveTransitionDuration : duration;
let idx = index;
this._col = this.__col;
for (let i = index; i < this.styleArr.length; i++) {
if (!this.styleArr[i] || !this.styleArr[i].complete) continue;
this.styleArr[i].width = this.colW + "px";
if (this.styleArr[i].showClass) {
this.styleArr[i]["transition-duration"] = `{duration}s`;
}
}
await this.nextTick();
for (let i = idx; i < this.styleArr.length; i++) {
if (!this.styleArr[i] || !this.styleArr[i].complete) return;
const e = this.getColDom(i);
if (!e) return;
// 获取当前元素高度
this.styleArr[i].height = e.offsetHeight;
let xy = this.getMinCol(i);
const curTop = xy.curTop + (index < this.__col ? 0 : this.gap),
curCol = xy.curCol,
curBT = curTop + this.styleArr[i].height + this.gap,
maxH = xy.maxH > curBT ? xy.maxH : curBT;
if (this.moveMode == "convention") {
this.styleArr[i].left = `{curCol * (this.colW + this.gap)}px`;
this.styleArr[i].top = `{curTop}px`;
} else {
this.styleArr[i].transform = `translate3d({
curCol * (this.colW + this.gap)
}px,{curTop}px ,0)`;
}
this.maxH = maxH;
this.styleArr[i].bottomTop = curBT;
this.styleArr[i].col = curCol;
this.styleArr[i].showClass = "show";
this.styleArr[i].state = "show";
}
this.forceUpdate();
this.loading = false; // 设置loading状态为false
await this.nextTick();
this.onRender &&
this.onRender({
cause: cause,
start: index,
});
},
getMinCol(curIndex) {
if (!curIndex) {
return {
curCol: 0,
curTop: 0,
maxH: 0,
};
}
let curCol = 0,
curTop = 0;
let set = {};
for (let index = curIndex - 1; index >= 0; index--) {
const item = this.styleArr[index];
if (item && !set[item.col]) {
set[item.col] = item;
}
if (Object.keys(set).length == this.__col) {
break;
}
}
let order = Object.values(set).sort((a, b) => a.bottomTop - b.bottomTop);
if (curIndex < this.__col) {
curCol = curIndex;
curTop = 0;
} else {
curCol = order[0].col;
curTop = order[0].bottomTop;
}
return {
curCol,
curTop,
maxH: order[order.length - 1].bottomTop,
};
},
loader(src, cb, imgDom = {}, i) {
if (imgDom.height > 0) {
return cb(imgDom);
}
if (!src && !imgDom.src) return cb();
if (imgDom.src) {
src = imgDom.src;
}
let img = loaderCache[src] && loaderCache[src].img;
if (img) {
if (img.complete || img.height > 0) return cb(img);
} else {
if (imgDom.src) {
img = imgDom;
} else {
img = new Image();
img.src = src;
}
if (img.complete || img.height > 0) return cb(img);
loaderCache[src] = {
img: img,
cbs: [],
i,
};
loaderImg.set(img.src, {
img,
cb: () => {
loaderCache[src].cbs.forEach((cb) => cb());
loaderCache[src].cbs.length = 0;
},
});
}
loaderCache[src].cbs.push(cb);
},
getColDom(i) {
return this.$refs["item" + i][0];
},
debounce(func, wait) {
let timer;
return function () {
let context = this;
let args = arguments;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, wait);
};
},
},
};
</script>
<style scoped>
.main {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
transition-property: height;
}
.main > .item {
position: absolute;
z-index: 1;
opacity: 0;
box-sizing: border-box;
transform: translate3d(0, 0, 0);
}
.convention {
transition-property: top, left;
}
.transform {
transition-property: transform;
}
.show {
opacity: 1 !important;
}
.main > .col {
float: left;
}
.col > .item {
width: 100%;
}
.autoExpand {
opacity: 0;
position: absolute;
left: -100%;
top: -100%;
width: 100%;
height: 100%;
visibility: hidden;
pointer-events: none;
}
</style>
使用
// 参数
//是否根据容器尺寸自动计算重绘
autoResize: true,
//是否始终填满容器
fillBox: true,
//列宽-有指定列数则此属性失效
colWidth: window.innerWidth / 5,
//列数
col: 6,
<div class="tab-container" id="tabContainer" @scroll="handleScroll">
<waterfall
:col="col"
:autoResize="autoResize"
:moveTransitionDuration="0.4"
:fillBox="fillBox"
:col-width="colWidth"
:list="meterialArray"
ref="waterfall"
imgKey="src"
>
<!-- 两种图片绑定模式
1.指定图片的Key( imgKey="src")
2.在img标签上加class( class="waterfall-img") -->
<!-- img标签如果设置宽高会渲染的更快 -->
<div
class="waterfall-item"
:class="{ bounceIn: item.state == 'show' }"
slot-scope="item"
@click="detailsClick(item.data, item.index)"
>
<div class="waterfall_box">
<img
v-if="item.data.src"
style="width: 100%"
class="waterfall-img"
:src="item.data.src"
/>
<div class="details_info" v-if="item.data.copywritingMaterial">
<div class="details_text">
{{ item.data.copywritingMaterial }}
</div>
</div>
</div>
<div class="list_box" v-if="item.data.src">
<img
v-if="item.data.type == 1"
src="@/assets/index/picture_icon.png"
alt=""
/>
<img v-else src="@/assets/index/video_icon.png" alt="" />
<div class="list_info">
<template v-if="item.data.type == 1">
{{ item.data.width }}*{{ item.data.height }}
</template>
<template v-else>
{{ item.data.fileSizeStr }} {{ item.data.durationStr }}
</template>
</div>
</div>
<div class="material_list">
<div class="title_box">
<div class="title_left">
{{ item.data.name }}
. {{ getFileExtension(item.data.baseMaterial) }}
</div>
<div class="title_right">
<img src="@/assets/index/more_icon.png" alt="" />
</div>
</div>
<div class="tip_box">
{{ item.data.applicationTypeName }}-{{
item.data.advertisingPositionTypeName
}}
</div>
</div>
</div>
</waterfall>
</div>
原地址:https://github.com/1977474741/vue-waterfall-rapid
Comments | NOTHING