效果图
实现方法
方法 | 特点 | 优点 | 缺点 |
---|---|---|---|
column-count | 顺序只能是 从上到下, 再左到右 | 纯css布局 代码少 实现快 | 由于排列顺序是先 从上到下, 再左到右,只能用于数据固定, 无法动态的加载追加,对于滚动到底部加载新数据则无法实现。 |
flex | 同样相对简单 | 纯css布局 代码少 实现快 | 需要指定高度,并且在每一列放置合适数量的图片,如果没有给容器设置固定宽度,则当容器宽度缩小时,图片的宽度相应的缩小,由于图片的高度是auto,即按图片原比例展示,因此图片高度会相应减少,由于高度指定,图片数量固定,则可能会出现图片占不满列数的情况 |
定位 | 可以实现分页加载 | 分页加载 | 相对来说比较复杂 每个div之间都需要计算 |
1.column-count 布局
.waterfall{
column-count: 4;//想要排成的列数
column-gap: 0;
.waterfall-item {
width: 100%;
// 有时候页面会出现在前几列的最后一个元素的内容被自动断开,一部分在当前列尾,一部分在下一列的列头。
break-inside: avoid; // 不被截断 默认值是auto,会被截断
.waterfall-img{
width: 100%;
}
}
}
2.flex 布局
.waterfall{
display: flex;
flex-direction: column;
flex-wrap: wrap;
/* 需要指定高度 */
height: 800px;
.waterfall-item{
width: 25%;
.waterfall-img{
width: 100%;
}
}
}
3.定位布局
<template>
<div class="waterfall">
<div
v-for="item in images"
:key="item.id"
:style="`width:${maxWidth}px`"
class="waterfall-item">
<div class="waterfall-box" :style="`height:${item._height}px`">
<!-- 限免标识 -->
<div class="waterfall-free">限免</div>
<!-- 图片 -->
<img class="waterfall-img" :src="item.image" alt="item" />
</div>
</div>
</div>
</template>
<script setup>
/**
* 父组件传递过来的数据
* @param images 数据列表
* @param column 列数
* @param maxWidth 最大宽度
* @param arrLength 当前数组长度
*/
const props = defineProps(['images','arrLength' ,'column', 'maxWidth'])
const data = reactive({
// 开始要排列的图片索引,首次为第二列的第一张图片,后续加载则为已经排列图片的下一个索引
beginIndex: 0,
// 高度数组
colsHeightArr: [],
// 预加载参数
loadedCount: 0,
})
const { beginIndex, colsHeightArr, loadedCount } = toRefs(data)
// 监听数据
watch(() => props.images, (news) => {
if (news.length == 0) return
preload()
})
// 有 tab 栏切换时需要把 beginIndex 值清空
const setBegin = () => {
beginIndex.value = 0
}
// 预加载
const preload = () => {
props.images.map((item, index) => {
// 只对新加载图片进行预加载
if (index < loadedCount.value) return;
let oImg = new Image();
oImg.src = item.image;
oImg.onload = oImg.onerror = (e) => {
item._height = e.type == "load"? Math.round(props.maxWidth * (oImg.height / oImg.width)) : props.maxWidth
}
// 判断预加载是否循环完成
if (props.arrLength == index+1) {
setTimeout(() => {
waterfall()
},50)
}
})
}
// waterfall布局
const waterfall = () => {
// 所有 waterfall-item 元素
let imgBoxEls = document.getElementsByClassName('waterfall-item')
// 如果没有元素 直接return
if (!imgBoxEls) return
// 每个图片最大宽度
let top, left, height, colWidth = props.maxWidth
// 索引是否为 0
if (beginIndex.value == 0) {
colsHeightArr.value = []
}
// 循环数据列表
for (let i = beginIndex.value; i < props.images.length; i++){
// 如果没有对应的元素 直接return
if (!imgBoxEls[i]) return
height = imgBoxEls[i].offsetHeight;
if (i < props.column) {
colsHeightArr.value.push(height)
top = 0;
left = i * colWidth;
} else {
// 最低高低
let minHeight = Math.min.apply(null, colsHeightArr.value)
// 最低高度的索引
let minIndex = colsHeightArr.value.indexOf(minHeight);
// 设置元素定位的位置
top = minHeight;
left = minIndex * colWidth;
// 更新colsHeightArr
colsHeightArr.value[minIndex] = minHeight + height;
}
imgBoxEls[i].style.left = `${left}px`;
imgBoxEls[i].style.top = `${top}px`;
beginIndex.value = props.images.length
}
}
defineExpose({
setBegin
})
</script>
<style lang="scss" scoped>
.waterfall{
width: 100%;
position: relative;
.waterfall-item{
position: absolute;
padding: 0 12px 12px 0;
box-sizing: border-box;
.waterfall-box{
height: 100%;
width: 100%;
overflow: hidden;
border-radius: 4px;
.waterfall-free{
position: absolute;
top: 5px;
left: 5px;
width: 54px;
height: 20px;
border-top-left-radius: 14px;
border-bottom-right-radius: 14px;
color: rgb(255, 255, 255);
background: linear-gradient(90deg, rgb(66, 66, 253) 0%, rgb(170, 103, 255) 100%, rgb(170, 103, 255) 100%);
text-align: center;
font-style: italic;
z-index: 10;
}
.waterfall-img{
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
transition: all 1s;
}
}
}
.waterfall-item .waterfall-img:hover {
transform: scale(1.2);
}
}
</style>
<Inspiration ref="waterfallRef" :arrLength="images.length" :maxWidth="300" :images="images" :column="4" ></Inspiration>
import Inspiration from '~/components/plaza/inspiration.vue'
const waterfallRef = ref()
// 调用方法
waterfallRef.value.setBegin()