vue 实现(抄袭)一个简单的栅格组件
参考iview, ant-design 的栅格组件,发现两者的基础思路是一致的。
这里通过实现一个简化版的栅格组件做总结.
目标
- 实现24格栅格布局,包括组件 Row(行), Col(列)
- 组件可嵌套, 容器大小自适应
- 响应式布局
基本原理
栅格组件,可以看成对横向宽度的分割。
例如:实现左右平分布局
// html
<div calss="container">
<div class="left" />
<div class="right" />
</div>
// css
.contianer{
display: flex;
justify-content: space-between;
}
.left,
.right{
flex: 0 0 auto;
width: 50%;
height: 100px;
}
.left{
background: blue;
}
.right{
background: orange;
}
实现栅格组件的基本思路与二等分基本一致,既是根据 Col组件不同的 span 值,动态的切换对应的等分样式, 所以重点在css配置
及如何切换
实现
这里基础布局会借用flex, css使用less编写,组件样式使用前缀做区分。
Row 基本结构
<template>
<div :class='classList' :style='style'>
// 内容插槽
<slot></slot>
</div>
</template>
<script lang='ts'>
import { Vue, Component, Prop, Provide } from 'vue-property-decorator'
const prefix = 'b-row'
@Component
export default class Row extends Vue {
// 组件类名
get classList (){
return [
prefix,
]
}
}
</script>
Col 基础结构
<template>
<div :class='classList'>
<slot></slot>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop, Inject, } from "vue-property-decorator"
const prefix = "b-col"
@Component
export default class Col extends Vue {
// 接口定义
@Prop() span?:number
@Prop() xs?:number
@Prop() sm?:number
@Prop() md?:number
get classList() {
return {
[prefix]: true
}
}
}
布局样式
这里我们希望通过设置 Col 的 span 属性分配列组件所占比例,
占满一行 span=24.
- 一种实现是通过js计算单个组件所占的比例,设置到容器行内样式上。
- 一种是将每一种分割类型的样式,都写入css, 通过类名的方式做区分
iview ant-design 使用的后者, 猜想原因: 一方面是对性能的考虑,另一方面从实际使用上,24切分基本满足大多数情况.
@prefix-row: ~'b-row';
@prefix-col: ~'b-col';
@grid-columns: 24;
// Row 基础样式
.@{prefix-row}{
position: relative;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
height: auto;
}
// Col 基础样式
.@{prefix-col}{
overflow: hidden;
flex: 0 0 auto;
box-sizing: border-box;
}
// 创建切分样式
// 因为每一切分的样式逻辑相同, 我们可以使用函数实现
.loop-grid-column(@index, @class) when(@index > 0) {
// 拼接类名 例如:b-col-span-1, 这里的 @class 主要为响应式设置不同的类名时使用
.@{prefix-col}-@{calss}-@{index}{
display: block;
box-sizing: border-box;
width: @index / @grid-columns * 100%;
}
// 递归调用
.loop-grid-column((@index - 1), @class)
}
// @index = 0 是的样式
.loop-grid-column(@index, @class) when(@index = 0) {
.@{prefix-col}-@{sizeType}-@{index}{
display: none;
}
}
/*
编译后:
.b-col-span-1{ ... }
.b-col-span-2{ ... }
.b-col-span-3{ ... }
.b-col-span-4{ ... }
.b-col-span-5{ ... }
.b-col-span-6{ ... }
.b-col-span-7{ ... }
....
*/
.make-grid(@class){
.loop-grid-column(@grid-columns, @class);
}
// span 切分
.make-grid(~'span')
// 响应式
@media (max-width: 575px) {
.makeGrid(~'xs')
}
@media (min-width: 576px) {
.makeGrid(~'sm')
}
@media (min-width: 768px) {
.makeGrid(~'md')
}
@media (min-width: 992px) {
.makeGrid(~'lg')
}
// 这里需要注意样式的先后顺序, 保证大尺寸样式覆盖小尺寸
设置动态类名
现在我们需要根据 span 或 响应配置 动态的设置 Col 的类名,以对应到不同的css样式上.
// col-html
<div :class='classList'>
<slot></slot>
</div>
// col-ts
get classList(){
let vm:any = this;
let { span, xs, sm, md } = this;
// 切分接口类型
let sizeType:string[] = ['xs', 'sm', 'md', 'span']
let typeClass = { [prefix]: true};
sizeType.forEach((type: string) =>{
let size = <number|undefined>(vm[type]);
let temp = {}
// 如果切分类型存在值, 拼接对应的类名
if(typeof size === 'number'){
temp = {
[`${prefix}-${type}-${size}`]: true
}
}
// 合并类名
typeClass = Object.assign(typeClass, temp)
})
return typeClass
}
/*
例如:
{
b-col-span-10: true,
b-col-xs-24: true,
b-col-sm-12: true
}
*/
完整例子
Row.vue
<template>
<div :class='classList' :style='style'>
<slot></slot>
</div>
</template>
<script lang='ts'>
import { Vue, Component, Prop, Provide } from 'vue-property-decorator'
const prefix = 'b-row'
@Component
export default class Row extends Vue {
@Provide() rowContext = this
@Prop({default: 0}) gutter!:number
private time:Date = new Date()
// computed
get classList (){
return [
prefix,
]
}
get style (){
return this.gutter && this.gutter > 0 ? {
'margin-left': `-${this.gutter/2}px`,
'margin-right': `-${this.gutter/2}px`
} : {}
}
}
</script>
Col.vue
<template>
<div :class='classList' :style='style'>
<slot></slot>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop, Inject, } from "vue-property-decorator"
const prefix = "b-col"
@Component
export default class Col extends Vue {
@Inject() rowContext?:any
@Prop() span?:number
@Prop() xs?:number
@Prop() sm?:number
@Prop() md?:number
@Prop() order?:number
get classList(){
let vm:any = this;
let { span, xs, sm, md } = this;
let sizeType:string[] = ['xs', 'sm', 'md', 'span']
let typeClass = { [prefix]: true};
sizeType.forEach((type: string) =>{
let size = <number|undefined>(vm[type]);
let temp = {}
if(typeof size === 'number'){
temp = {
[`${prefix}-${type}-${size}`]: true
}
}
typeClass = Object.assign(typeClass, temp)
})
return typeClass
}
get style(){
let order = this.order;
let gutter = this.rowContext.gutter;
let baseStyle = {
'order': `${order !== undefined ? order : 0 }`
};
let pdStyle = {};
if(gutter > 0){
pdStyle = {
'padding-left': `${gutter/2}px`,
'padding-right': `${gutter/2}px`,
}
}
return Object.assign({}, baseStyle, pdStyle);
}
}
</script>
index.less
@prefix-row: ~'b-row';
@prefix-col: ~'b-col';
@gird-columns: 24;
.loop-grid-columns(@index, @sizeType) when (@index > 0){
.@{prefix-col}-@{sizeType}-@{index}{
display: block;
box-sizing: border-box;
width: @index / @gird-columns * 100%;
}
.loop-grid-columns((@index - 1), @sizeType)
}
.loop-grid-columns(@index, @sizeType) when (@index = 0){
.@{prefix-col}-@{sizeType}-@{index}{
display: none;
}
}
.clearfix{
&::after{
content: '';
display: table;
}
clear: both;
}
.mackRow(@gutter:0){
.clearfix;
position: relative;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
margin-left: (@gutter/-2)px;
margin-right: (@gutter/-2)px;
height: auto;
}
.@{prefix-col}{
overflow: hidden;
flex: 0 0 auto;
box-sizing: border-box;
}
// flex 水平布局
@justify: center, flex-start, flex-end, space-between, space-around;
.loop-flex-justify(@i: 1) when (@i < length(@justify) + 1) {
@type: extract(@justify, @i);
@prop: ~'[justify="@{type}"]';
&@{prop}{
justify-content: @type;
}
.loop-flex-justify(@i + 1)
}
// flex 垂直布局
@align: flex-start, flex-end, center, baseline, stretch;
.loop-flex-align(@i: 1) when (@i < length(@align) + 1){
@type: extract(@align, @i);
@prop: ~'[align="@{type}"]';
&@{prop}{
align-items: @type;
}
.loop-flex-align((@i + 1))
}
.@{prefix-row}{
.mackRow();
box-sizing: border-box;
background: #eee;
.loop-flex-justify();
.loop-flex-align();
}
.makeGrid(@class){
.loop-grid-columns(@gird-columns, @class);
}
.makeGrid(~'span');
@media (max-width: 575px) {
.makeGrid(~'xs')
}
@media (min-width: 576px) {
.makeGrid(~'sm')
}
@media (min-width: 768px) {
.makeGrid(~'md')
}
@media (min-width: 992px) {
.makeGrid(~'lg')
}
网友评论