这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
在主流的 Vue 组件库中,layout 组件是最基础的组件之一,用于快速布局页面 UI 和制作响应式系统,在这篇文章将带你着手设计并实现一个基础的 layout 组件。
组件设计
我们以 elementUI 为例,可以分析出 layout 组件分为两部分:row和col,也就是行和列,row支持定义布局方式,分栏间隔和 flex 布局下的排列方式等。col支持设置栏位数和偏移量。那么基于此,我们可以设计以下组件内容:
基础概念
分栏间隔
我们将每行分为 24 栏位,也就是 24 列,分栏间隔即为每一列之间的间隙大小,这里以 px 为单位。
布局方式
默认为普通布局,可选 flex 布局方式,方便更灵活的控制各元素的排列方式。
栏位数
作用于 col 组件中,表示元素所占的列数。
偏移量
元素默认从起始位置开始布局,如果希望元素向左偏移一定的距离,可以传入偏移量属性控制。
Row 组件
组件除了支持传入栏位间隔和布局方式外,还支持定义在 flex 布局下的水平、垂直排列方式,可选值对应的是 css 属性的justify-content和align-items的值。具体内容如下:
name: "bp-row",
props: {
gutter: { type: [Number, String], default: 0 }, // 分栏间隔
type: {
type: String,
default: "",
validator: function (value) {
return ["", "flex"].indexOf(value) !== -1;
},
}, // 布局方式
justify: {
type: String,
default: "center",
validator: function (value) {
return (
["start", "end", "center", "space-around", "space-between"].indexOf(
value
) !== -1
);
},
}, // flex下的水平排列方式
align: {
type: String,
default: "middle",
validator: function (value) {
return ["top", "middle", "bottom"].indexOf(value) !== -1;
},
}, // flex下的垂直排列方式
}
Col 组件
col 接收两个属性,分别是栏位数和偏移量。
name: "bp-col",
props: {
span: { type: [Number, String], default: 0 }, // 栏位数
offset: { type: [Number, String], default: 0 }, // 偏移量
},
Row 组件的实现
首先是模板,只需要设置外层 div 即可,内容由插槽填充。如下:
<template>
<div :class="clazzName" ref="row">
<slot></slot>
</div>
</template>
由于组件支持两种布局方式,所以外层的样式需要额外控制,如下:
const clazzName = computed(() => {
const isFlex = props.type === "flex";
const prefix = isFlex ? "bp-row-flex" : "bp-row";
const name = [prefix];
if(isFlex){
name.push(`bp-row-flex-justify-${props.justify}`)
name.push(`bp-row-flex-align-${props.align}`)
}
return name;
});
flex 布局下,水平、排列的样式属性需要由 props 去支持 。对应的样式如下:
.bp-row {
position: relative;
box-sizing: border-box;
display: block;
}
.bp-row:after,
.bp-row:before {
display: table;
content: ""
}
.bp-row:after {
clear: both
}
// flex 布局
.bp-row-flex {
display: flex;
}
.bp-row-flex-justify-start {
justify-content: flex-start;
}
.bp-row-flex-justify-end {
justify-content: flex-end;
}
.bp-row-flex-justify-center {
justify-content: center;
}
.bp-row-flex-justify-space-around {
justify-content: space-around;
}
.bp-row-flex-justify-space-between {
justify-content: space-between;
}
.bp-row-flex-justify-space-evenly {
justify-content: space-evenly;
}
.bp-row-flex-align-top {
align-items: flex-start;
}
.bp-row-flex-align-middle {
align-items: center;
}
.bp-row-flex-align-bottom {
align-items: flex-end;
}
此外,针对栏位间隔的实现,需要手动获取子元素的所有 col 元素,并挨个遍历设置 padding 样式,具体实现如下:
// 设置 col 属性
const setColAttrs = () => {
// 获取 row 下所有 col
const row = getCurrentInstance().refs.row.children || [];
let len = row.length;
if (len === 0) return;
for (let i = 0; i < len; i++) {
// 布局模式
row[i].classList.add("bp-col");
// Gutter 处理
if (props.gutter !== 0 && len > 1) {
if (i !== 0) row[i].style.paddingLeft = `${props.gutter}px`;
if (i !== len - 1) row[i].style.paddingRight = `${props.gutter}px`;
}
}
};
onMounted(() => {
setColAttrs();
});
Col 组件的实现
模板准备,同样,只需设置外层样式即可。如下:
<template>
<div :class="colClassName">
<slot></slot>
</div>
</template>
Col 组件的逻辑只需要处理栏位数和偏移量,生成对应的样式类即可,剩余的在样式文件中实现。
let colClassName = computed(() => {
// 默认样式和前缀
let prefix = "bp-col";
let className = [];
Number(props.span) !== 0 ? className.push(`${prefix}-${props.span}`) : "";
// 偏移量
Number(props.offset) !== 0
? className.push(`${prefix}-offset-${props.offset}`)
: "";
return className;
});
关于样式,这里使用的是 less 实现,不然的话,手动一条条去写也是可以的。
[class*=bp-col-] {
float: left;
box-sizing: border-box;
}
.bp-col {
box-sizing: border-box;
}
// 总宽
@width : 100%;
// 总栏数
.total-nums(24);
.total-nums(@n, @i: 1) when (@i =< @n) {
// ============= 分栏 ===============
.bp-col-@{i} {
width: @width / (24 / @i);
display: block;
}
// ============= 偏移 ===============
.bp-col-offset-@{i} {
margin-left: @width / (24 / @i);
}
.total-nums(@n, (@i + 1));
}
DEMO
<bp-row :gutter="10">
<bp-col :span="8" title="span=8"><div class="row-demo bg-blue-5"></div></bp-col>
<bp-col :span="16" title="span=16"><div class="row-demo bg-blue-5"></div></bp-col>
</bp-row>
<bp-row :gutter="10">
<bp-col :span="3" title="span=3"><div class="row-demo bg-blue-5"></div></bp-col>
<bp-col :span="10" title="span=10"><div class="row-demo bg-blue-5"></div></bp-col>
<bp-col :span="11" title="span=11"><div class="row-demo bg-blue-5"></div></bp-col>
</bp-row>
<bp-row :gutter="10">
<bp-col :span="4" title="span=4"><div class="row-demo bg-blue-5"></div></bp-col>
<bp-col :span="8" title="span=8"><div class="row-demo bg-blue-5"></div></bp-col>
<bp-col :span="3" title="span=3"><div class="row-demo bg-blue-5"></div></bp-col>
<bp-col :span="9" title="span=9"><div class="row-demo bg-blue-5"></div></bp-col>
</bp-row>