Table of Contents

前言

MaterialUI算是现在比较常见的UIKit了,用的人多,社区比较大,而且各种环境和框架的移植也很丰富,用起来比较简单和方便。当然使用的时候因为一些特有的概念和语法糖,还是需要一点上手时间。

官方的教程:Tutorial(基本上没用)。

一些资源:

Notes

样式

直接的样式处理及样式的编程,见文档:@material-ui/styles。简单范例:

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const useStyles = makeStyles({
  root: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    border: 0,
    borderRadius: 3,
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
    color: 'white',
    height: 48,
    padding: '0 30px',
  },
});

export default function Hook() {
  const classes = useStyles();
  return <Button className={classes.root}>Hook</Button>;
}

栅格系统

栅格系统是布局的基础,官方文档在:Grid 栅格。当然,MaterialUI也提供了直接使用Flexbox来进行布局自定义的能力。另外,在做响应式布局的时候,Hidden组件也是非常重要的,能在某些分辨率下隐藏一些组件的显示。

栅格系统使用 Grid “盒子”组件实现:

  • 为了达到高度的灵活性,盒子组件运用了用 CSS 的 Flexible Box 模块 。
  • 它有两种类型的布局:containers,items。
  • 而项目宽度以百分比设置,因此相对于其父元素,它们总是流动的和变换大小的。
  • 子元素则自带 padding 来和其他元素间隔。
  • 你可以找到五个网格断点:xs,sm,md,lg 和 xl。

网格断点

首先需要了解响应式布局的网格断点,见文档:Breakpoints。要注意,虽然断点的尺寸是有预定义值的,但也是可以自定义改动的。

每个断点(一个键)匹配一个固定的屏幕宽度(一个值):

  • xs,超小:0px
  • sm,小:600px
  • md,中等:960px
  • lg,大:1280px
  • xl,超大:1920px
value         |0px     600px    960px    1280px   1920px
key           |xs      sm       md       lg       xl
screen width  |--------|--------|--------|--------|-------->
range         |   xs   |   sm   |   md   |   lg   |   xl
// 样式的屏幕尺寸感知,并自适应
const styles = theme => ({
  root: {
    padding: theme.spacing(1),
    [theme.breakpoints.down('sm')]: {
      backgroundColor: theme.palette.secondary.main,
    },
    [theme.breakpoints.up('md')]: {
      backgroundColor: theme.palette.primary.main,
    },
    [theme.breakpoints.up('lg')]: {
      backgroundColor: green[500],
    },
  },
});

断点隐藏

一般在做响应式设计的时候,会用到Hidden组件,该组件有3组属性可以控制当前组件在某些断点情况下的显示和隐藏:

  • *Up:使用任何断点up属性的元素,给定的 子节点 将在断点以及断点以上时被隐藏。
  • *Down:使用任何断点down属性的元素,给定 子节点 将在断点以及断点以下时被隐藏。
  • *Only:利用断点only属性,给定 子节点 将被隐藏在指定的断点
innerWidth  |xs      sm       md       lg       xl
            |--------|--------|--------|--------|-------->
width       |   xs   |   sm   |   md   |   lg   |   xl

smUp        |   show | hide
mdDown      |                     hide | show

看几个例子:

Up
*Up:使用任何断点up属性的元素,给定的 子节点 将在断点以及断点以上时被隐藏。playground:链接

<div className={classes.container}>
  <Hidden xsUp>
    <Paper className={classes.paper}>xsUp</Paper>
  </Hidden>
  <Hidden smUp>
    <Paper className={classes.paper}>smUp</Paper>
  </Hidden>
  <Hidden mdUp>
    <Paper className={classes.paper}>mdUp</Paper>
  </Hidden>
  <Hidden lgUp>
    <Paper className={classes.paper}>lgUp</Paper>
  </Hidden>
  <Hidden xlUp>
    <Paper className={classes.paper}>xlUp</Paper>
  </Hidden>
</div>

效果:

Down
*Down:使用任何断点down属性的元素,给定 子节点 将在断点以及断点以下时被隐藏。playground:链接

<div className={classes.container}>
  <Hidden xsDown>
    <Paper className={classes.paper}>xsDown</Paper>
  </Hidden>
  <Hidden smDown>
    <Paper className={classes.paper}>smDown</Paper>
  </Hidden>
  <Hidden mdDown>
    <Paper className={classes.paper}>mdDown</Paper>
  </Hidden>
  <Hidden lgDown>
    <Paper className={classes.paper}>lgDown</Paper>
  </Hidden>
  <Hidden xlDown>
    <Paper className={classes.paper}>xlDown</Paper>
  </Hidden>
</div>

效果:

Only
*Only:利用断点only属性,给定 子节点 将被隐藏在指定的断点。playground:链接

<div className={classes.container}>
  <Hidden only="lg">
    <Paper className={classes.paper}>Hidden on lg</Paper>
  </Hidden>
  <Hidden only="sm">
    <Paper className={classes.paper}>Hidden on sm</Paper>
  </Hidden>
  <Hidden only={['sm', 'lg']}>
    <Paper className={classes.paper}>Hidden on sm and lg</Paper>
  </Hidden>
</div>

效果:

流式网格

实际组件的空间占用布局是按Fluid grids 流式网格来排放的。举个例子:

import React from 'react';
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      flexGrow: 1,
    },
    paper: {
      padding: theme.spacing(2),
      textAlign: 'center',
      color: theme.palette.text.secondary,
    },
  }),
);

export default function CenteredGrid() {
  const classes = useStyles();

  return (
    <div className={classes.root}>
      <Grid container spacing={3}>
        <Grid item xs={12}>
          <Paper className={classes.paper}>xs=12</Paper>
        </Grid>
        <Grid item xs={6}>
          <Paper className={classes.paper}>xs=6</Paper>
        </Grid>
        <Grid item xs={6}>
          <Paper className={classes.paper}>xs=6</Paper>
        </Grid>
        <Grid item xs={3}>
          <Paper className={classes.paper}>xs=3</Paper>
        </Grid>
        <Grid item xs={3}>
          <Paper className={classes.paper}>xs=3</Paper>
        </Grid>
        <Grid item xs={3}>
          <Paper className={classes.paper}>xs=3</Paper>
        </Grid>
        <Grid item xs={3}>
          <Paper className={classes.paper}>xs=3</Paper>
        </Grid>
      </Grid>
    </div>
  );
}

效果:

例子中的xs是指前面提到的断点。只有屏幕尺寸在xs及其上的断点的情况下,才会按此布局。里面的数值是相对的,第一行最大值为12,后面第二行的6就是其一半。这个例子的playground:链接

定义尺寸的时候可以对一个组件设置多个网格断点情况下的大小,这样做的话,可以让组件排布顺应不同的屏幕尺寸发生变化,看下面的例子:

例子中,在屏幕尺寸大于sm的情况下(从大到小匹配),sm的尺寸定义发挥作用:第二行的组件占比都为6,第二行2个组件各占一半,同样的第三行占比设为3,一行正好4个组件都放进去。

然后在屏幕尺寸缩小之后,尺寸小于sm,且大于xs,则xs的尺寸定义发挥作用:原来第二行各分一半的2个组件分别占了一行,原来第三行的4个组件分为了两行,每行两个。这个例子的playground:链接

常用容器

Container

页面的主要容器,一般来说所有的内容都会放在Container内。Container的最大宽度,使用maxWidth来设置,里面的值需要设置刚才的断点值:xs、sm、md、lg、xl。另,Container的API文档,属性相关内容都在这里。

<Container maxWidth="sm" />

Box

包装容器,一般用来将某几个组件包装在一起做位置偏移或样式添加等操作。Box默认为一个div,也可以使用component来设置实际的HTML标签:

<Box component="span" m={1}>
  <Button />
</Box>

Grid

最基本的容器组件,通过将该组件相互嵌套使用,就可以组成非常复杂的页面布局。另,Grid的API文档,组件属性需要到这里查阅。

嵌套的情况下,container属性表示是包在外层的Grid,item则表示是从属于外层包装的Grid。如果有多层嵌套,则中间层的Grid既是container又是item。看个例子,playground在链接

// defining FormRow
function FormRow() {
  return (
    <React.Fragment>
      <Grid item xs={4}>
        <Paper className={classes.paper}>item</Paper>
      </Grid>
      <Grid item xs={4}>
        <Paper className={classes.paper}>item</Paper>
      </Grid>
      <Grid item xs={4}>
        <Paper className={classes.paper}>item</Paper>
      </Grid>
    </React.Fragment>
  );
}

// using FormRow
<Grid container spacing={1}>
  <Grid container item xs={12} spacing={3}>
    <FormRow />
  </Grid>
  <Grid container item xs={12} spacing={3}>
    <FormRow />
  </Grid>
  <Grid container item xs={12} spacing={3}>
    <FormRow />
  </Grid>
</Grid>

效果:

间距设置

在MaterialUI中设置间距有已经做好的语法糖,直接使用即可,文档:Spacing,还有Theme Spacing

其中属性是其中之一:

  • m - 对于设置margin
  • p - 对于设置padding

哪边边是其中之一:

  • t - 对于设置* margin-top或padding-top*的类
  • b - 对于设置margin-bottom的类或padding-bottom的类
  • l - 对于设置margin-left或padding-left的类
  • r - 对于设置margin-right或padding-right的类
  • x - 对于设置* -left和* -right的类
  • y - 对于设置* -top和* -bottom的类
  • 空白 - 用于在元素的所有4个边上设置边距或填充的类

上面提到的属性的值,是数字,意思是几份。比如说my={1}就是上下添加一份的margin。下面的例子中,一份就是8px(MaterialUI的默认值一份就是8px)。

const theme = {
  spacing: 8,
}

<Box m={-2} />  // margin: -16px;
<Box m={0} />   // margin: 0px;
<Box m={0.5} /> // margin: 4px;
<Box m={2} />   // margin: 16px;

宽高尺寸设置

见文档:Sizing

<Box width={1/4} /> // Numbers in [0,1] are multiplied by 100 and converted to % values.
<Box width={300} /> // Numbers are converted to pixel values.
<Box width="75%" /> // String values are used as raw CSS.
<Box width={1} />   // 100%

颜色系统

MaterialUI提供了配色相关的解决方案,见:Color 颜色(如果需要选择颜色代码的话,就直接在这个文档页面上查找),以及Theme Palette,以及Palette 调色

<Box color="warning.main" />
<Box bgcolor="info.main" />

阴影

MaterialUI的阴影直接使用属性boxShadow就能实现了,文档:Shadow

<Box boxShadow={1} />

层级

有时候一些元素会在同位置上进行重叠,这时候就需要确定它们的层级来进行相互覆盖的定义。在CSS中我们会使用z-index来操作,在MaterialUI中,一般使用属性zIndex来操作,且MaterialUI已经定义了一些,一般直接使用即可,范例

<Box zIndex="tooltip" />
<Box zIndex="modal" />

预定义数值

  • mobile stepper(移动设备起步): 1000
  • speed dial: 1050
  • app bar(应用栏):1100
  • drawer(抽屉):1200
  • modal(浮层):1300
  • snackbar:1400
  • tooltip(提示):1500

文字铸排

组件内文字内容的Typography,MaterialUI有属性可以直接控制,不需要手动编写CSS。见文档:Typography,以及Theme Typography

<Box textAlign="left" />                // right, left, center
<Box fontWeight="fontWeightRegular" />  // light, regular, medium, 500, bold
<Box fontSize="fontSize" />             // default, h6.fontSize, 16
<Box fontStyle="normal" />              // normal, italic, oblique
<Box letterSpacing={6} />               // 字符间距,这里的数值就是6px
<Box lineHeight="normal" />             // normal, 10

如果需要在页面上直接展示文字,建议使用组件Typography,见文档:Typography。此外,还有API文档,组件的属性需要到这里查阅。

看一个例子,该例子的playground在:链接

<Grid item xs={12} sm container>
  <Grid item xs container direction="column" spacing={2}>
    <Grid item xs>
      <Typography gutterBottom variant="subtitle1">
        Standard license
      </Typography>
      <Typography variant="body2" gutterBottom>
        Full resolution 1920x1080 • JPEG
      </Typography>
      <Typography variant="body2" color="textSecondary">
        ID: 1030114
      </Typography>
    </Grid>
    <Grid item>
      <Typography variant="body2" style=>
        Remove
      </Typography>
    </Grid>
  </Grid>
  <Grid item>
    <Typography variant="subtitle1">$19.00</Typography>
  </Grid>
</Grid>

效果:

该组件的noWrap属性默认值为false,也就是说遇到文字过长的情况下会进行回行。如果将这个属性设置为true,则文字过长的部分会被截断,并显示为省略号。

统一风格系统

MaterialUI有一套统一的设计方案,方便开发者在App级别对所有的组件进行统一的样式及风格定义。官方文档:@material-ui/system。一般来说项目大的话,肯定有需要进行这部分的调整。如果使用的是官方Store中的一些Template,一般它们已经做好了对应的功能。

这部分比较庞大,细节很多。这里放一个简单例子,直观感受下:

import React from 'react'
import { ThemeProvider } from 'styled-components'

const theme = {
  spacing: 4,
  palette: {
    primary: '#007bff',
  },
};

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      {/* children */}
    </ThemeProvider>
  )
}

更多的细节可以查看MaterialUI的Customization个性化文档。

Typescript

MaterialUI对Typescript支持很好,官方文档:TypeScript。里面细节还是有点多的,需要慢慢习惯用法。

EOF