24 栅格系统
概述
布局的栅格化系统,我们是基于行(Row)和列(Col)两个组件相互配合来定义信息区块的外部框架,以保证页面的每个区域能够稳健地排布起来。
下面简单介绍一下它的工作原理:
- 通过
Row
在水平方向建立一组Col
- 你的内容应当放置于
Col
内,并且,只有Col
可以作为Row
的直接元素 - 栅格系统中的列是指 1 到 24 的值来表示其跨越的范围。例如,三个等宽的- 列可以使用
:span="8"
来创建 - 如果一个
Row
中的Col
总和超过 24,那么多余的Col
会作为一个整体另起一行排列
Flex 布局
我们的栅格化系统支持 Flex 布局,允许子元素在父节点内的水平对齐方式 - 居左、居中、居右、等宽排列、分散排列。子元素与子元素之间,支持顶部对齐、垂直居中对齐、底部对齐的方式。同时,支持使用 order 来定义元素的排列顺序。 Flex 布局是基于 24 栅格来定义每一个『盒子』的宽度,但不拘泥于栅格
😁 整体功能效果与 ant design vue
保持高度一致 😁
1. 行组件Row.vue
可自定义设置以下属性:
- 行宽度(width),类型:string|number,默认:‘auto’
- 栅格间隔(gutter),类型:number|[number|Responsive, number|Responsive]|Responsive,默认值:0。可以写成像素值或支持响应式的对象写法来设置水平间隔 { xs: 8, sm: 16, md: 24}。或者使用数组形式同时设置 [水平间距, 垂直间距],推荐使用 (16+8n)px 作为栅格间隔(n 是自然数:0,1,2,3…)
- 是否自动换行(wrap),类型:boolean,默认值:false
- 垂直对齐方式(align),类型:‘top’|‘middle’|‘bottom’|‘stretch’,默认:‘top’
- 水平排列方式(justify),类型:‘start’|‘end’|‘center’|‘space-around’|‘space-between’|‘space-evenly’,默认:‘start’
其中 type Responsive
interface Responsive {
xs?: number // <576px 响应式栅格
sm?: number // ≥576px 响应式栅格
md?: number // ≥768px 响应式栅格
lg?: number // ≥992px 响应式栅格
xl?: number // ≥1200px 响应式栅格
xxl?: number // ≥1600px 响应式栅格
}
2. 列组件Col.vue
可自定义设置以下属性:
- 栅格占位格数(span),类型:number,默认:undefined。为 0 时相当于 display: none,取0,1,2…24
- 栅格左侧的间隔格数(offset),类型:number,默认:0。取0,1,2…24
- flex 布局填充(flex),类型:string|number,默认:‘’
- <576px 响应式栅格(xs),类型:number|{span: number, offset?: number},默认:undefined
- ≥576px 响应式栅格(sm),类型:number|{span: number, offset?: number},默认:undefined
- ≥768px 响应式栅格(md),类型:number|{span: number, offset?: number},默认:undefined
- ≥992px 响应式栅格(lg),类型:number|{span: number, offset?: number},默认:undefined
- ≥1200px 响应式栅格(xl),类型:number|{span: number, offset?: number},默认:undefined
- ≥1600px 响应式栅格(xxl),类型:number|{span: number, offset?: number},默认:undefined
效果如下图:
创建行组件Row.vue
<script setup lang="ts">
import { onMounted, onUnmounted, ref, computed } from 'vue'
interface Responsive {
xs?: number // <576px 响应式栅格
sm?: number // ≥576px 响应式栅格
md?: number // ≥768px 响应式栅格
lg?: number // ≥992px 响应式栅格
xl?: number // ≥1200px 响应式栅格
xxl?: number // ≥1600px 响应式栅格
}
interface Props {
width?: string|number // 行宽度
// 推荐使用 (16+8n)px 作为栅格间隔(n 是自然数:0,1,2,3...)
gutter?: number|[number|Responsive, number|Responsive]|Responsive // 栅格间隔,可以写成像素值或支持响应式的对象写法来设置水平间隔 { xs: 8, sm: 16, md: 24}。或者使用数组形式同时设置 [水平间距, 垂直间距]
wrap?: boolean // 是否自动换行
align?: 'top'|'middle'|'bottom'|'stretch' // 垂直对齐方式
justify?: 'start'|'end'|'center'|'space-around'|'space-between'|'space-evenly' // 水平排列方式
}
const props = withDefaults(defineProps<Props>(), {
width: 'auto',
gutter: 0,
wrap: false,
align: 'top',
justify: 'start'
})
const alignProperties = {
top: 'flex-start',
middle: 'center',
bottom: 'flex-end',
stretch: 'stretch'
}
const xGap = computed(() => {
if (typeof props.gutter === 'number') {
return props.gutter
}
if (Array.isArray(props.gutter)) {
if (typeof props.gutter[0] === 'object') {
if (clientWidth.value >= 1600 && props.gutter[0].xxl) {
return props.gutter[0].xxl
}
if (clientWidth.value >= 1200 && props.gutter[0].xl) {
return props.gutter[0].xl
}
if (clientWidth.value >= 992 && props.gutter[0].lg) {
return props.gutter[0].lg
}
if (clientWidth.value >= 768 && props.gutter[0].md) {
return props.gutter[0].md
}
if (clientWidth.value >= 576 && props.gutter[0].sm) {
return props.gutter[0].sm
}
if (clientWidth.value < 576 && props.gutter[0].xs) {
return props.gutter[0].xs
}
}
return props.gutter[0]
}
if (typeof props.gutter === 'object') {
if (clientWidth.value >= 1600 && props.gutter.xxl) {
return props.gutter.xxl
}
if (clientWidth.value >= 1200 && props.gutter.xl) {
return props.gutter.xl
}
if (clientWidth.value >= 992 && props.gutter.lg) {
return props.gutter.lg
}
if (clientWidth.value >= 768 && props.gutter.md) {
return props.gutter.md
}
if (clientWidth.value >= 576 && props.gutter.sm) {
return props.gutter.sm
}
if (clientWidth.value < 576 && props.gutter.xs) {
return props.gutter.xs
}
}
return 0
})
const yGap = computed(() => {
if (Array.isArray(props.gutter)) {
if (typeof props.gutter[1] === 'object') {
if (clientWidth.value >= 1600 && props.gutter[1].xxl) {
return props.gutter[1].xxl
}
if (clientWidth.value >= 1200 && props.gutter[1].xl) {
return props.gutter[1].xl
}
if (clientWidth.value >= 992 && props.gutter[1].lg) {
return props.gutter[1].lg
}
if (clientWidth.value >= 768 && props.gutter[1].md) {
return props.gutter[1].md
}
if (clientWidth.value >= 576 && props.gutter[1].sm) {
return props.gutter[1].sm
}
if (clientWidth.value < 576 && props.gutter[1].xs) {
return props.gutter[1].xs
}
}
return props.gutter[1]
}
return 0
})
const rowWidth = computed(() => {
if (typeof props.width === 'number') {
return props.width + 'px'
}
return props.width
})
const clientWidth = ref(document.documentElement.clientWidth)
onMounted(() => {
window.addEventListener('resize', getBrowserSize)
})
onUnmounted(() => {
window.removeEventListener('resize', getBrowserSize)
})
function getBrowserSize () {
// document.documentElement返回<html>元素
clientWidth.value = document.documentElement.clientWidth
}
</script>
<template>
<div
class="m-row"
:class="{'gutter-row': gutter}"
:style="`--xGap: ${(xGap as number) / 2}px; --justify: ${justify}; --align: ${alignProperties[align]}; width: ${rowWidth}; margin-left: -${(xGap as number) / 2}px; margin-right: -${(xGap as number) / 2}px; row-gap: ${yGap}px;`">
<slot></slot>
</div>
</template>
<style lang="less" scoped>
.m-row {
display: flex;
flex-flow: row wrap;
justify-content: var(--justify);
align-items: var(--align);
min-width: 0;
font-size: 14px;
color: rgba(0, 0, 0, 0.88);
transition: all .3s;
}
</style>
创建列组件Col.vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
interface Props {
span?: number // 栅格占位格数,为 0 时相当于 display: none,取0,1,2...24
offset?: number // 栅格左侧的间隔格数,取0,1,2...24
flex?: string|number // flex 布局填充
xs?: number|{span: number, offset?: number} // <576px 响应式栅格
sm?: number|{span: number, offset?: number} // ≥576px 响应式栅格
md?: number|{span: number, offset?: number} // ≥768px 响应式栅格
lg?: number|{span: number, offset?: number} // ≥992px 响应式栅格
xl?: number|{span: number, offset?: number} // ≥1200px 响应式栅格
xxl?: number|{span: number, offset?: number} // ≥1600px 响应式栅格
}
const props = withDefaults(defineProps<Props>(), {
span: undefined,
offset: 0,
flex: '',
xs: undefined,
sm: undefined,
md: undefined,
lg: undefined,
xl: undefined,
xxl: undefined
})
const flexValue = computed(() => {
if (typeof props.flex === 'number') {
return `${props.flex} ${props.flex} auto`
}
return props.flex
})
const responsiveProperty = computed(() => {
if (clientWidth.value >= 1600 && props.xxl) {
if (typeof props.xxl === 'object') {
return props.xxl
} else {
return {
span: props.xxl,
offset: undefined
}
}
}
if (clientWidth.value >= 1200 && props.xl) {
if (typeof props.xl === 'object') {
return props.xl
} else {
return {
span: props.xl,
offset: undefined
}
}
}
if (clientWidth.value >= 992 && props.lg) {
if (typeof props.lg === 'object') {
return props.lg
} else {
return {
span: props.lg,
offset: undefined
}
}
}
if (clientWidth.value >= 768 && props.md) {
if (typeof props.md === 'object') {
return props.md
} else {
return {
span: props.md,
offset: undefined
}
}
}
if (clientWidth.value >= 576 && props.sm) {
if (typeof props.sm === 'object') {
return props.sm
} else {
return {
span: props.sm,
offset: undefined
}
}
}
if (clientWidth.value < 576 && props.xs) {
if (typeof props.xs === 'object') {
return props.xs
} else {
return {
span: props.xs,
offset: undefined
}
}
}
})
const clientWidth = ref(document.documentElement.clientWidth)
onMounted(() => {
window.addEventListener('resize', getBrowserSize)
})
onUnmounted(() => {
window.removeEventListener('resize', getBrowserSize)
})
function getBrowserSize () {
// document.documentElement返回<html>元素
clientWidth.value = document.documentElement.clientWidth
}
</script>
<template>
<div
:class="`m-col col-${responsiveProperty?.span || span} offset-${responsiveProperty?.offset || offset}`"
style="padding-left: var(--xGap); padding-right: var(--xGap);"
:style="`flex: ${flexValue}`">
<slot></slot>
</div>
</template>
<style lang="less" scoped>
.m-col {
position: relative;
max-width: 100%;
min-height: 1px;
font-size: 14px;
color: rgba(0, 0, 0, 0.88);
line-height: 1.5714285714285714;
transition: all .3s;
}
.col-0 {
display: none;
}
.col-1 {
display: block;
flex: 0 0 4.16666666666666%;
max-width: 4.16666666666666%;
}
.offset-1 {
margin-inline-start: 4.16666666666666%;
}
.col-2 {
display: block;
flex: 0 0 8.33333333333333%;
max-width: 8.33333333333333%;
}
.offset-2 {
margin-inline-start: 8.33333333333333%;
}
.col-3 {
display: block;
flex: 0 0 12.5%;
max-width: 12.5%;
}
.offset-3 {
margin-inline-start: 12.5%;
}
.col-4 {
display: block;
flex: 0 0 16.66666666666666%;
max-width: 16.66666666666666%;
}
.offset-4 {
margin-inline-start: 16.66666666666666%;
}
.col-5 {
display: block;
flex: 0 0 20.83333333333333%;
max-width: 20.83333333333333%;
}
.offset-5 {
margin-inline-start: 20.83333333333333%;
}
.col-6 {
display: block;
flex: 0 0 25%;
max-width: 25%;
}
.offset-6 {
margin-inline-start: 25%;
}
.col-7 {
display: block;
flex: 0 0 29.16666666666666%;
max-width: 29.16666666666666%;
}
.offset-7 {
margin-inline-start: 29.16666666666666%;
}
.col-8 {
display: block;
flex: 0 0 33.33333333333333%;
max-width: 33.33333333333333%;
}
.offset-8 {
margin-inline-start: 33.33333333333333%;
}
.col-9 {
display: block;
flex: 0 0 37.5%;
max-width: 37.5%;
}
.offset-9 {
margin-inline-start: 37.5%;
}
.col-10 {
display: block;
flex: 0 0 41.66666666666666%;
max-width: 41.66666666666666%;
}
.offset-10 {
margin-inline-start: 41.66666666666666%;
}
.col-11 {
display: block;
flex: 0 0 45.83333333333333%;
max-width: 45.83333333333333%;
}
.offset-11 {
margin-inline-start: 45.83333333333333%;
}
.col-12 {
display: block;
flex: 0 0 50%;
max-width: 50%;
}
.offset-12 {
margin-inline-start: 50%;
}
.col-13 {
display: block;
flex: 0 0 54.16666666666666%;
max-width: 54.16666666666666%;
}
.offset-13 {
margin-inline-start: 54.16666666666666%;
}
.col-14 {
display: block;
flex: 0 0 58.33333333333333%;
max-width: 58.33333333333333%;
}
.offset-14 {
margin-inline-start: 58.33333333333333%;
}
.col-15 {
display: block;
flex: 0 0 62.5%;
max-width: 62.5%;
}
.offset-15 {
margin-inline-start: 62.5%;
}
.col-16 {
display: block;
flex: 0 0 66.66666666666666%;
max-width: 66.66666666666666%;
}
.offset-16 {
margin-inline-start: 66.6666666666666%;
}
.col-17 {
display: block;
flex: 0 0 70.83333333333333%;
max-width: 70.83333333333333%;
}
.offset-17 {
margin-inline-start: 70.83333333333333%;
}
.col-18 {
display: block;
flex: 0 0 75%;
max-width: 75%;
}
.offset-18 {
margin-inline-start: 75%;
}
.col-19 {
display: block;
flex: 0 0 79.16666666666666%;
max-width: 79.16666666666666%;
}
.offset-19 {
margin-inline-start: 79.16666666666666%;
}
.col-20 {
display: block;
flex: 0 0 83.33333333333333%;
max-width: 83.33333333333333%;
}
.offset-20 {
margin-inline-start: 83.33333333333333%;
}
.col-21 {
display: block;
flex: 0 0 87.5%;
max-width: 87.5%;
}
.offset-21 {
margin-inline-start: 87.5%;
}
.col-22 {
display: block;
flex: 0 0 91.66666666666666%;
max-width: 91.66666666666666%;
}
.offset-22 {
margin-inline-start: 91.66666666666666%;
}
.col-23 {
display: block;
flex: 0 0 95.83333333333333%;
max-width: 95.83333333333333%;
}
.offset-23 {
margin-inline-start: 95.83333333333333%;
}
.col-24 {
display: block;
flex: 0 0 100%;
max-width: 100%;
}
.offset-24 {
margin-inline-start: 100%;
}
</style>
在要使用的页面引入
<script setup lang="ts">
import Row from './Row.vue'
import Col from './Col.vue'
</script>
<template>
<div>
<h1>Grid 栅格</h1>
<h2 class="mt30 mb10">基本使用</h2>
<Row class="row">
<Col :span="24">col</Col>
</Row>
<Row class="row">
<Col :span="12">col-12</Col>
<Col :span="12">col-12</Col>
</Row>
<Row class="row">
<Col :span="8">col-8</Col>
<Col :span="8">col-8</Col>
<Col :span="8">col-8</Col>
</Row>
<Row class="row">
<Col :span="6">col-6</Col>
<Col :span="6">col-6</Col>
<Col :span="6">col-6</Col>
<Col :span="6">col-6</Col>
</Row>
<h2 class="mt30 mb10">水平区块间隔</h2>
<Row :gutter="16">
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
</Row>
<h2 class="mt30 mb10">响应式区块间隔</h2>
<Row :gutter="{ xs: 8, sm: 16, md: 24, lg: 32 }">
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
</Row>
<h2 class="mt30 mb10">垂直区块间隔</h2>
<Row :gutter="[16, 24]">
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
</Row>
<h2 class="mt30 mb10">响应式垂直区块间隔</h2>
<Row :gutter="[16, { xs: 8, sm: 16, md: 24, lg: 32 }]">
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
<Col class="gutter-row" :span="6">
<div class="gutter-box">col-6</div>
</Col>
</Row>
<h2 class="mt30 mb10">左右偏移</h2>
<Row class="row">
<Col :span="8">col-8</Col>
<Col :span="8" :offset="8">col-8</Col>
</Row>
<Row class="row">
<Col :span="6" :offset="6">col-6 col-offset-6</Col>
<Col :span="6" :offset="6">col-6 col-offset-6</Col>
</Row>
<Row class="row">
<Col :span="12" :offset="6">col-12 col-offset-6</Col>
</Row>
<h2 class="mt30 mb10">排版方式</h2>
<Divider orientation="left">sub-element align left</Divider>
<Row class="row" justify="start">
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
</Row>
<Divider orientation="left">sub-element align center</Divider>
<Row class="row" justify="center">
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
</Row>
<Divider orientation="left">sub-element align right</Divider>
<Row class="row" justify="end">
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
</Row>
<Divider orientation="left">sub-element align space-between</Divider>
<Row class="row" justify="space-between">
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
</Row>
<Divider orientation="left">sub-element align space-around</Divider>
<Row class="row" justify="space-around">
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
</Row>
<Divider orientation="left">sub-element align space-evenly</Divider>
<Row class="row" justify="space-evenly">
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
<Col :span="4">col-4</Col>
</Row>
<h2 class="mt30 mb10">对齐方式</h2>
<Divider orientation="left">Align Top</Divider>
<Row class="row" style="background: rgba(128,128,128,.08);" justify="center" align="top">
<Col :span="4">
<p class="height-100">col-4</p>
</Col>
<Col :span="4">
<p class="height-50">col-4</p>
</Col>
<Col :span="4">
<p class="height-120">col-4</p>
</Col>
<Col :span="4">
<p class="height-80">col-4</p>
</Col>
</Row>
<Divider orientation="left">Align Middle</Divider>
<Row class="row" style="background: rgba(128,128,128,.08);" justify="space-around" align="middle">
<Col :span="4">
<p class="height-100">col-4</p>
</Col>
<Col :span="4">
<p class="height-50">col-4</p>
</Col>
<Col :span="4">
<p class="height-120">col-4</p>
</Col>
<Col :span="4">
<p class="height-80">col-4</p>
</Col>
</Row>
<Divider orientation="left">Align Bottom</Divider>
<Row class="row" style="background: rgba(128,128,128,.08);" justify="space-between" align="bottom">
<Col :span="4">
<p class="height-100">col-4</p>
</Col>
<Col :span="4">
<p class="height-50">col-4</p>
</Col>
<Col :span="4">
<p class="height-120">col-4</p>
</Col>
<Col :span="4">
<p class="height-80">col-4</p>
</Col>
</Row>
<h2 class="mt30 mb10">flex 填充</h2>
<Divider orientation="left">Percentage columns</Divider>
<Row class="row">
<Col :flex="2">2 / 5</Col>
<Col :flex="3">3 / 5</Col>
</Row>
<Divider orientation="left">Fill rest</Divider>
<Row class="row">
<Col flex="0 0 100px">100px</Col>
<Col flex="auto">auto</Col>
</Row>
<Divider orientation="left">Raw flex style</Divider>
<Row class="row">
<Col flex="1 1 200px">1 1 200px</Col>
<Col flex="0 1 300px">0 1 300px</Col>
</Row>
<Row class="row" :wrap="false">
<Col flex="none">
<div style="padding: 0 16px">none</div>
</Col>
<Col flex="auto">auto with no-wrap</Col>
</Row>
<h2 class="mt30 mb10">响应式布局</h2>
<Row class="row">
<Col :xs="2" :sm="4" :md="6" :lg="8" :xl="10">Col</Col>
<Col :xs="20" :sm="16" :md="12" :lg="8" :xl="4">Col</Col>
<Col :xs="2" :sm="4" :md="6" :lg="8" :xl="10">Col</Col>
</Row>
<h2 class="mt30 mb10">span 和 offset 属性的响应式</h2>
<Row class="row">
<Col :xs="{ span: 5, offset: 1 }" :sm="{ span: 6, offset: 2 }">Col</Col>
<Col :xs="{ span: 11, offset: 1 }" :sm="{ span: 6, offset: 2 }">Col</Col>
<Col :xs="{ span: 5, offset: 1 }" :sm="{ span: 6, offset: 2 }">Col</Col>
</Row>
</div>
</template>
<style lang="less" scoped>
.row {
.m-col {
min-height: 30px;
margin-top: 8px;
margin-bottom: 8px;
color: #fff;
text-align: center;
border-radius: 0;
padding: 16px 0;
background: #1677ff;
}
:deep(>:nth-child(2n+1)) {
background: #1677ffbf;
}
}
.gutter-row {
.m-col {
min-height: 30px;
margin-top: 8px;
margin-bottom: 8px;
color: #fff;
text-align: center;
border-radius: 0;
}
.gutter-box {
background: #0092ff;
padding: 8px 0;
}
.m-col {
margin-top: 0;
margin-bottom: 0;
}
}
.height-50 {
height: 50px;
line-height: 50px;
}
.height-80 {
height: 80px;
line-height: 80px;
}
.height-100 {
height: 100px;
line-height: 100px;
}
.height-120 {
height: 120px;
line-height: 120px;
}
</style>