前两天在公司的一个手机点餐项目组帮了几天忙,发现里面的点餐页面使用了瀑布流。我一直知道瀑布流布局但没有具体实现过,这两天下班抽空简单实现了一下。
<template>
<div class="home">
<div
class="wrap"
@scroll="move"
ref="wrap">
<div
v-for="(item, idx) in list"
:key="idx"
class="item"
:style="{
'height': item.num + 'px',
'left': item.x + 'px',
'top': item.y + 'px',
'background-color': item.y > scrollTop - item.num + 100 && item.y < scrollTop + 1000 - 100 ? 'red' : '#666'
}">
<!-- 上面因为好观察效果,上下各留了100px的缓冲区 -->
{{item.num}}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'waterfall',
data () {
return {
list: [],
scrollTop: 0
}
},
created () {
// 生成随机数(随机数为方块的高)
let random = []
for (let i = 0; i < 1000; i++) {
random.push(Math.floor(Math.random() * 10) * 10 + 100)
}
// 定义初始位置(这里预留的20px的margin)
let first = 20
let second = 20
// 计算每个方块的位置并添加到list里面
for (const num of random) {
let x = 20
let y = 0
if (first <= second) { // 第一列
y = first
first += num + 20
} else { // 第二列
x = 290
y = second
second += num + 20
}
this.list.push({
x,
y,
num
})
}
},
methods: {
// 不优化
move (e) {
const wrap = this.$refs.wrap
this.scrollTop = wrap.scrollTop
}
}
}
</script>
<style scoped>
.wrap {
width: 560px;
height: 1000px;
overflow: auto;
position: relative;
background-color: rgb(222,222,222)
}
.item {
width: 250px;
position: absolute;
border-radius: 10px;
}
</style>
看代码应该很容易理解,但还是简单介绍一下,这是一个用绝对定位实现的瀑布流,外面有一个560 * 1000的盒子包裹,通过随机数生成里面item的高度,通过高度计算出每个item的绝对位置,计算过程大家自己看下吧,挺简单的,下面是实现结果。
大家看到肯定很奇怪,为啥有红的有灰的?这里就要说一下长列表渲染的优化了,那个点餐项目的菜品非常多,而且需要在首页全部展示,这么多的dom渲染起来对前端压力非常大,我就想能不能只渲染可视区域内的菜品,于是就通过@scroll事件在滑动中拿到容器的scrollTop,再结合每个item之前计算出来的绝对位置得出每个item该不该显示,这里红色的就是可视区域内的item,在这里我为了方便更直观的观察到可视区域与非可视区域,把可视区域上下各缩小了100px。
虽然在显示上做过了优化,但每次滑动事件都触发每个item的计算来判断该不该显示,这个计算频率对前端来说也太高了,所以我又在此基础上进行了再一次的优化。
<template>
<div class="home">
<div
class="wrap"
@scroll="move"
ref="wrap">
<div
v-for="(item, idx) in list"
:key="idx"
class="item"
:style="{
'height': item.num + 'px',
'left': item.x + 'px',
'top': item.y + 'px',
'background-color': item.y > scrollTop - item.num - 2000 && item.y < scrollTop + 1000 + 2000 ? 'red' : '#666'
}">
{{item.num}}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'waterfall',
data () {
return {
list: [],
scrollTop: 0,
counter: 0
}
},
created () {
// 生成随机数(随机数为方块的高)
let random = []
for (let i = 0; i < 100000; i++) {
random.push(Math.floor(Math.random() * 10) * 10 + 100)
}
// 定义初始位置(这里预留的20px的margin)
let first = 20
let second = 20
// 计算每个方块的位置并添加到list里面
for (const num of random) {
let x = 20
let y = 0
if (first <= second) { // 第一列
y = first
first += num + 20
} else { // 第二列
x = 290
y = second
second += num + 20
}
this.list.push({
x,
y,
num
})
}
},
methods: {
// 计数器节流
move () {
this.counter++
if (this.counter < 5) return
const wrap = this.$refs.wrap
this.scrollTop = wrap.scrollTop
if (this.counter >= 5) this.counter = 0
}
}
}
</script>
<style scoped>
.wrap {
width: 560px;
height: 1000px;
overflow: auto;
position: relative;
background-color: rgb(222,222,222)
}
.item {
width: 250px;
position: absolute;
border-radius: 10px;
}
</style>
这个优化主要是对滑动事件进行了节流处理,加了个计数器节流,合并五次滑动事件为一次滑动事件,这样就大大减少了前端的计算量,同时为了防止快速大幅度滑动而导致没有真正触发滑动事件的计算而现实不出来菜单,我把渲染区域的上下各加了2000px,相当于渲染区域从原来的一页变成了五页,比起减少了五分之四的计算量,多渲染的四页dom元素根本算不了什么。
嗯,划了划还有那么点意思。
这只是一次简单的实现及优化,肯定有许多不足的地方,接下来的时间如果我遇到更好的方法也会分享出来,也欢迎大家提出宝贵意见。