前端九宫格的实现

1,582 阅读2分钟

如何实现一个类似以下图片的九宫格?

拿到UI设计图的时候,感觉很简单,就是一个容器里面放几个子项(加上对应),然后flex布局就解决,当我写完的时候,发现好像不对,里面的每一项的边框并不符合UI图,仔细观察这个图我们可以发现每个外围的子项边框并不是完整的,需要单独处理子项才能达到和UI完全一致的效果

我们来一起分析每个子项(item)种类的边框类型:

第一种:只需要右边框和下边框(对应上图中的1, 2, 4, 5)

第二种:只需要下边框(对应上图3, 6)

第三种:只需要右边框(对应上图7, 8)

第四种:无边框(对应上图9)

有些同学就会问了---哎,为什么没有上边框和左边框?因为使用右下边框就可以模拟出来这个了,左上边框是和右下边框同理对称的我们这里使用右下边框来做分类!

子项边框分类的表格

子项(序号)上边框右边框下边框左边框
1
2
3
4
5
6
7
8
9
**现在来写判断每一个子项的边框样式的方法,需要提前写好class样式,```&.border-right {
			border-right: 1upx solid #F8F8F8;
		}
		&.border-bottom {
			border-bottom: 1upx solid #F8F8F8;
		}```**
    // 计算是否需要右边框和左边框
	getBorderClass(index) { // index代表子项的索引值
	    index += 1
	    let className = ''
	    let lineNumber = this.lineNumber // 每行行显示个数
	    let total = this.list.length // 总个数
	    let line = Math.ceil(total / lineNumber) // 一共有几行
	    let currentLine = 0 // 当前在第几行,这里从1开始
	    let currentIndex = 0 // 当前在某行的第几个
	    currentLine = Math.ceil(index / lineNumber)
	    currentIndex = (index % lineNumber) === 0 ? lineNumber : index % lineNumber
	    // 右下边框
	    if (currentLine < line && currentIndex < lineNumber) {
	        className = 'border-bottom border-right'
	    }
	    // 右边框
	    if (currentLine === line && currentIndex < lineNumber) {
	        className = 'border-right'
	    }
	    // 下边框
	    if (currentLine < line && currentIndex === lineNumber) {
	        className = 'border-bottom'
	        
	    }
	    // 无边框
	    if (currentLine === line && currentIndex === lineNumber) {
	        className = ''
	    }
	    return className
	}

填充空白子项问题

我们拿到后台菜单数据是不一个定长的数组,假设现在就拿到了一个length = 7的菜单数组,我现在需要渲染出来九个格子,那我们就需要计算当前拿到的数组和填满9九宫格需要的另外两个空白格(不是杨宗纬的空白格)。

这里我们要考虑到每行显示的个数问题,有可能每行只显示3个也有可能4个...,那我们就把每行显示多少个写成一个变量参数lineNumber = 3,初始化需要填充的空白格为fillNumber = 0, 菜单数组为list,list长度未知,为后台返回,从这里我们就可以写出计算空白格的fn

// 计算空白格
getFillNumber(length, lineNumber = 3) {
    let remainder = length % lineNumber
    return remainder === 0 ? 0 : lineNumber - remainder // 这里需要注意如果是整除之后就不需要空白格了
},

完整代码

<template>
	<view class="app-card-content">
		<view
			:style="'width: ' + (100/lineNumber - 0.5) + '%'"
			:class="['app-card-content_item', getBorderClass(index)]"
			v-for="(item, index) in list"
			@click="itemClick(item)"
			:key='index'>
			<view v-if="item.imgSrc">
				<image :src="item.imgSrc" mode=""></image>
			</view>
			<text v-if="item.title">
				{{item.title}}
			</text>
		</view>
	</view>
</template>

<script>
	import List from '@/config/application.js'
	export default {
		data() {
			return {
				list: [],
				fillNumber: 0, // 填充空白格的数量
				lineNumber: 4
			}
		},
		mounted() {
			// 接口调用后再调用以下方法
			this.fillNumber = this.getFillNumber(this.list.length)
			this.list = this.fillArray(this.list, this.lineNumber)
		},
		methods: {
			// 计算多余的填充框
			getFillNumber(length, lineNumber = 3) {
				let remainder = length % lineNumber
				return remainder === 0 ? 0 : lineNumber - remainder
			},
			// 不满整除的数组需要进行填充满
			fillArray(arr, lineNumber = 3) {
				// 余数
				let remainder = this.getFillNumber(arr.length, this.lineNumber)
				if (remainder === 0) return arr
				for (let i = 0; i < remainder; i++) {
					// 填充空对象
					arr.push({})
				}
				return arr
			},
			// 计算是否需要右边框和左边框
			getBorderClass(index) {
				index += 1
				let className = ''
				let lineNumber = this.lineNumber // 每行行显示个数
				let total = this.list.length // 总个数
				let line = Math.ceil(total / lineNumber) // 一共有几行
				let currentLine = 0 // 当前在第几行,这里从1开始
				let currentIndex = 0 // 当前在某行的第几个
				currentLine = Math.ceil(index / lineNumber)
				currentIndex = (index % lineNumber) === 0 ? lineNumber : index % lineNumber
				// 右下边框
				if (currentLine < line && currentIndex < lineNumber) {
					className = 'border-bottom border-right'
				}
				// 右边框
				if (currentLine === line && currentIndex < lineNumber) {
					className = 'border-right'
				}
				// 下边框
				if (currentLine < line && currentIndex === lineNumber) {
					className = 'border-bottom'
				}
				// 无边框
				if (currentLine === line && currentIndex === lineNumber) {
					className = ''
				}
				return className
			}
		}
	}
</script>

<style lang="scss">
	.app-card-content {
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		background-color: #fff;
		.app-card-content_item {
			/* width: 25%; */
			height: 200upx;
			display: flex;
			align-items: center;
			justify-content: center;
			flex-direction: column;
			&.border-right {
				border-right: 1upx solid #F8F8F8;
			}
			&.border-bottom {
				border-bottom: 1upx solid #F8F8F8;
			}
			view {
				margin-bottom: 10upx;
				image {
					width: 50upx;
					height: 50upx;
				}
			}
		}
	}
</style>
因为需求改动,最后改成了每行显示四个,最终效果图如下

最终效果

最终效果