微信小程式實戰,基於vue2實現瀑布流

2022-12-02 06:00:20

1、什麼是瀑布流呢?

瀑布流,又稱瀑布流式佈局。是比較流行的一種網站頁面佈局,視覺表現為參差不齊的多欄佈局,隨著頁面卷軸向下捲動,這種佈局還會不斷載入資料塊並附加至當前尾部。

瀑布流對於圖片的展現,是高效而具有吸引力的,使用者一眼掃過的快速閱讀模式可以在短時間內獲得更多的資訊量,而瀑布流裡懶載入模式又避免了使用者滑鼠點選的翻頁操作,瀑布流的主要特性便是錯落有致,定寬而不定高的設計讓頁面區別於傳統的矩陣式圖片佈局模式,巧妙的利用視覺層級,視線的任意流動又緩解了視覺疲勞,同時給人以不拘一格的感覺,切中年輕一族的個性化心理。

下面這些就是用瀑布流來實現,看起來是不是很美觀呢?

2、實現一個簡單的瀑布流

先看一下咱們最終的試下效果吧,只是簡單傳入文字進行演示

1、瀑布流的特點

1、琳琅滿目:整版以圖片為主,大小不一的圖片按照一定的規律排列。

2、唯美:圖片的風格以唯美的圖片為主。

3、操作簡單:在瀏覽網站的時候只需要輕輕滑動一下滑鼠滾輪,一切的美妙的圖片精彩便可呈現在你面前

2、核心演演算法

通過圖片我們可以直觀的看到,每一個卡片的高度都是不一樣的,需要我們實時能計算高度,同時左右的高度還是不能相互影響。

這裡我們主要通過兩個陣列進行實現,即分為左右陣列,核心程式碼如下:

<view id="u-left-column" class="u-column">
	<slot name="left" :leftList="leftList"></slot>
</view>
<view id="u-right-column" class="u-column">
	<slot name="right" :rightList="rightList"></slot>
</view>

data() {
	return {
		leftList: [],
		rightList: [],
		tempList: [],
		scrollTop: 0,
	}
}

對傳入陣列進行分組和計算高度

async splitData() {
	if (!this.tempList.length) return;
	let leftRect = await this.$uGetRect('#u-left-column');
	let rightRect = await this.$uGetRect('#u-right-column');
	// 如果左邊小於或等於右邊,就新增到左邊,否則新增到右邊
	let item = this.tempList[0];
	// 解決多次快速上拉後,可能資料會亂的問題,因為經過上面的兩個await節點查詢阻塞一定時間,加上後面的定時器干擾
	// 陣列可能變成[],導致此item值可能為undefined
	if (!item) return;
	if (leftRect.height < rightRect.height) {
		this.leftList.push(item);
	} else if (leftRect.height > rightRect.height) {
		this.rightList.push(item);
	} else {
		// 這裡是為了保證第一和第二張新增時,左右都能有內容
		// 因為新增第一張,實際佇列的高度可能還是0,這時需要根據佇列元素長度判斷下一個該放哪邊
		if (this.leftList.length <= this.rightList.length) {
			this.leftList.push(item);
		} else {
			this.rightList.push(item);
		}
	}
	// 移除臨時列表的第一項
	this.tempList.splice(0, 1);
	// 如果臨時陣列還有資料,繼續迴圈
	if (this.tempList.length) {
		this.splitData();
		return
	}
}

3、完整的元件程式碼如下

<template>
	<scroll-view class="scroll-y" scroll-y="true" @scrolltolower="tolower" :scroll-top="scrollTop">
		<view class="u-waterfall" id="list">
			<view id="u-left-column" class="u-column">
				<slot name="left" :leftList="leftList"></slot>
			</view>
			<view id="u-right-column" class="u-column">
				<slot name="right" :rightList="rightList"></slot>
			</view>
		</view>
	</scroll-view>
</template>

<script>
	export default {
		name: "waterfall",
		props: {
			value: {
				// 瀑布流資料
				type: Array,
				required: true,
				default: function() {
					return [];
				}
			},
			scrolltolower: {
				type: Function,
				default: () => {}
			}
		},
		data() {
			return {
				leftList: [],
				rightList: [],
				tempList: [],
				scrollTop: 0,
			}
		},
		watch: {
			copyFlowList(nVal, oVal) {
				this.tempList = this.cloneData(this.copyFlowList);
				this.splitData();
			}
		},
		mounted() {
			this.tempList = this.cloneData(this.copyFlowList);
			this.splitData();
			// this.$on('clearWaterFall', this.clear)
		},
		computed: {
			// 破壞flowList變數的參照,否則watch的結果新舊值是一樣的
			copyFlowList() {
				return this.cloneData(this.value);
			}
		},
		methods: {
			async splitData() {
				if (!this.tempList.length) return;
				let leftRect = await this.$uGetRect('#u-left-column');
				let rightRect = await this.$uGetRect('#u-right-column');
				// 如果左邊小於或等於右邊,就新增到左邊,否則新增到右邊
				let item = this.tempList[0];
				// 解決多次快速上拉後,可能資料會亂的問題,因為經過上面的兩個await節點查詢阻塞一定時間,加上後面的定時器干擾
				// 陣列可能變成[],導致此item值可能為undefined
				if (!item) return;
				if (leftRect.height < rightRect.height) {
					this.leftList.push(item);
				} else if (leftRect.height > rightRect.height) {
					this.rightList.push(item);
				} else {
					// 這裡是為了保證第一和第二張新增時,左右都能有內容
					// 因為新增第一張,實際佇列的高度可能還是0,這時需要根據佇列元素長度判斷下一個該放哪邊
					if (this.leftList.length <= this.rightList.length) {
						this.leftList.push(item);
					} else {
						this.rightList.push(item);
					}
				}
				// 移除臨時列表的第一項
				this.tempList.splice(0, 1);
				// 如果臨時陣列還有資料,繼續迴圈
				if (this.tempList.length) {
					this.splitData();
					return
				}
			},
			// 複製而不是參照物件和陣列
			cloneData(data) {
				return JSON.parse(JSON.stringify(data));
			},
			tolower(e) {
				this.scrolltolower()
			},
			clear() {
				this.leftList = []
				this.rightList = []
			}
		}
	}
</script>

<style lang="scss" scoped>
	@mixin vue-flex($direction: row) {
		/* #ifndef APP-NVUE */
		display: flex;
		flex-direction: $direction;
		/* #endif */
	}

	.scroll-y {
		height: 78vh;
		margin-top: 18px;
	}

	.u-waterfall {
		@include vue-flex;
		flex-direction: row;
		align-items: flex-start;
	}

	.u-column {
		@include vue-flex;
		flex: 1;
		flex-direction: column;
		height: auto;
		width: 45vw;
		word-break: break-all;
	}
</style>

3、簡單使用

基於vue的語法進行使用,先進行匯入和註冊

<script>
import waterfall from '../../component/waterfall/index.vue'
export default {
	name: 'content',
	components: {
		waterfall
	}
}
</script>

因為元件是基於插槽的形式進行開發的,所以我們可以直接傳入咱們的樣式和標籤

<template>
	<view class="main">
		<waterfall :value="dataList" :scrolltolower="getRecommendLove" ref="child">
			<template v-slot:left="left">
				<view v-for="item in left.leftList" :key="item.id" class="left-content" @click="copy(item)">
					<view class="item">
						{{item.content}}
					</view>
				</view>
			</template>
			<template v-slot:right="right">
				<view v-for="item in right.rightList" :key="item.id" class="right-content" @click="copy(item)">
					<view class="item">
						{{item.content}}
					</view>
				</view>
			</template>
		</waterfall>
	</view>
</template>

最終的效果就可以達到我們的目標了