前兩天在公司的一個手機點餐項目組幫了幾天忙,發現裏面的點餐頁面使用了瀑布流。我一直知道瀑布流佈局但沒有具體實現過,這兩天下班抽空簡單實現了一下。
<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元素根本算不了什麼。
嗯,劃了劃還有那麼點意思。
這只是一次簡單的實現及優化,肯定有許多不足的地方,接下來的時間如果我遇到更好的方法也會分享出來,也歡迎大家提出寶貴意見。