46

Angular Material 的设计之美 - 叙帝利

 4 years ago
source link: https://www.cnblogs.com/nzbin/p/11424801.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Angular Material 的设计之美

999445-20190904145716535-1633080146.jpg

Ng-Matero 0.3 已发布,新增 module schematic 以及 page schematic,详见 README

Angular Material 作为 Angular 的官方组件库,无论是设计交互还是易用性都有着极高的质量。正如官方所说其目的就是构建基于 Angular 和 TypeScript 的高质量组件库。

官方列举了如下几点来解释“高质量”的含义。

  • 国际化和可访问性,以便所有用户都可以使用。
  • 不会让开发人员感到困惑的简单 API。
  • 在各种各样没有 bug 的用例中按预期行事。
  • 通过单元测试和集成测试更好地测试行为。
  • 可在 Material Design 规范的范围内进行定制。
  • 将性能开销降至最低。
  • 代码简洁,文档友好,可以作为 Angular 开发人员的一个例子。

Material Design 作为一个非常流行的设计语言,它有多个版本的实现。React 版的 Material Design 有着很高的人气,大家可以自行对比,我就不赘述了,以免引起无谓的争吵,进而扯到框架层面。我可以说一下自己的感受,Angular Material 的交互更加流畅,细节做的更好。

Angular Material 组件库虽然很优秀,但是却被戴上了只适合做 C 端界面的帽子。这也是我刚开始不敢选择 Angular Material 的一个原因。但是在编写 ng-matero 的过程中,随着对 Angular Material 的深入了解,我发现这种说法稍显狭隘甚至产生了一定的误导,所以我希望这篇文章可以让大家对 Angular Material 有一个更加正确的认识。接下来我会从相对宏观的角度介绍 Angular Material 设计的一些亮点,并且简单介绍 Angular Material 的一些使用技巧。

题外话:为什么 ng-matero 会选择 Angular Material?

抛开官方提到的几点不谈。首先我是那种比较激进的开发者,对于先进的设计理念,我都有跃跃欲试的执念。国内的 Element UI 以及 Ant Design 都是 Bootstrap 3 时代的风格。随着业务人员对界面细致紧凑的要求越来越高,我发现 Material 的设计风格更加符合需求,层次感更强。不过最主要的还是 Material Design 的交互更吸引我。另外,Angular Material 的样式是基于 Sass 编写,而我最喜欢的也是 Sass,所以基于 Angular Material 编写 ng-matero 就是宿命的选择。顺便插一句,如果大家纠结用 Sass 还是 Less,可以看一下这篇文章 CSS 预处理器中的循环,个人不建议用 Less,请原谅我无意引战🤪。

Less is more(少即是多)—— 密斯·凡德罗

我想很多人对 Angular Material 望而却步的原因之一就是它的组件看上去有点少。然而在一般的业务中这些组件已经够用。除了常用组件之外,Angular Material 还有一个组件开发包 CDK。在设计界有一句名言“少即是多”,苹果的产品就是最好的证明。把这句名言用在 Angular Material 上丝毫不为过,其实除了我们看到的组件之外,Material 还有一些隐藏组件,比如可以用 menu 组件构造 popover,我会在下文中介绍。

丰富的颜色

999445-20190904132552681-2098143922.jpg

Material Design 的亮点之一就是拥有非常丰富的颜色值,其实 Angular Material 的颜色变量比官方定义的色值还要多一些。大家可以点击 ng-matero 的 colors 页面 查看。ng-matero 也有所有颜色值对应的 colors helper,可以更加方便的创建丰富多彩的按钮或标签。Angular Material 的颜色定义严谨且优雅。以下是红色值的变量。

$mat-red: (
  50: #ffebee,
  100: #ffcdd2,
  200: #ef9a9a,
  300: #e57373,
  400: #ef5350,
  500: #f44336,
  600: #e53935,
  700: #d32f2f,
  800: #c62828,
  900: #b71c1c,
  A100: #ff8a80,
  A200: #ff5252,
  A400: #ff1744,
  A700: #d50000,
  contrast: (
    50: $dark-primary-text,
    100: $dark-primary-text,
    200: $dark-primary-text,
    300: $dark-primary-text,
    400: $dark-primary-text,
    500: $light-primary-text,
    600: $light-primary-text,
    700: $light-primary-text,
    800: $light-primary-text,
    900: $light-primary-text,
    A100: $dark-primary-text,
    A200: $light-primary-text,
    A400: $light-primary-text,
    A700: $light-primary-text,
  )
);

除了定义基础色值之外,还有相对应的文本色定义,非常严谨。我在以前写 helper 库 的时候,曾写过颜色集群,文本色处理都是一刀切,非常不严谨,所以感触非常深。更惊喜的的是 Angular Material 甚至给出了灰色值的别名。

// Alias for alternate spelling.
$mat-gray: $mat-grey;

灵活的主题定制

999445-20190906190635352-467363504.jpg

Angular Material 的样式几乎全部写在了 mixin 中,定制起来非常容易。我最开始认为将所有样式全部写到 mixin 中并不是很优雅的做法,但是在编写 ng-matero 暗黑主题的时候,我发现不这样做是不行的。以下是 Angular Material 主题定制的方法。

@import '~@angular/material/theming';

// Include non-theme styles for core.
@include mat-core();

// Define a theme.
$candy-app-primary: mat-palette($mat-indigo);
$candy-app-accent:  mat-palette($mat-pink, A200, A100, A400);

$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);

// Include all theme styles for the components.
@include angular-material-theme($candy-app-theme);

Angular Material 给出了多套主题的设置方法,只需要增加样式控制类就可以了。

// Define an alternate dark theme.
$dark-primary: mat-palette($mat-blue-grey);
$dark-accent:  mat-palette($mat-amber, A200, A100, A400);
$dark-warn:    mat-palette($mat-deep-orange);
$dark-theme:   mat-dark-theme($dark-primary, $dark-accent, $dark-warn);

// `.unicorn-dark-theme` will be affected by this alternate dark theme instead of the default theme.
.unicorn-dark-theme {
  @include angular-material-theme($dark-theme);
}

在此我简单介绍一下 ng-matero 的主题切换。增加样式控制类可以说是最简单的主题切换方式,但是缺点就是同时拥有多套主题,代码量太大。如果只作为 DEMO 展示是没问题的,但是生产环境不推荐这样做。

ng-matero 在使用 ng add 初始化的时候增加了预构建主题选项,生成的主题只有一份,如果有特殊需求可以自行定制。实现方式就是不同主题传入不同变量,但是这种情况下多主题控制会有问题。所以必须使用 mixin 编写某些样式,这样的话就可以有局部变量环境。如下:

.theme-dark {
  $primary: mat-palette($mat-pink, 700, 500, 900);
  $accent: mat-palette($mat-blue-grey, A200, A100, A400);
  $warn: mat-palette($mat-red);
  $theme: mat-dark-theme($primary, $accent, $warn);

  @include angular-material-theme($theme);
  @include matero-admin-theme($theme);
}

Angular Material 提供了几乎所有和 Material Design 有关的样式工具,包括variablefunctionmixin,都可以在 theming 文件中找到。

除了上面提到的主题定制 functionmixin 之外,我们还可以使用 mat-elevation() 轻松制作 MD 阴影。另外我们还可以使用 $swift-ease-out-timing-function$mat-fast-out-slow-in-timing-function 这些动画变量实现和 MD 一样的动画效果。

基于这套工具集,我们可以很容易的搭建和 MD 风格相统一的界面。

极简的 API

Angular Material 的官方文档可能稍微不太友好,总感觉内容很多,看不进去。但是耐心看一下,就会发现其简洁之道,Angular Material 的 API 也是“少即是多”的一种表现。以表单组件为例,以下是一个滑块组件。

<mat-slide-toggle [(ngModel)]="options.model"
                  (change)="changeOptions()"
                  [disabled]="options.disabled">visible
</mat-slide-toggle>

Angular Material 的表单组件更像是对原生 html 元素的复写。在熟悉了一种组件之后,几乎不需要额外的记忆成本,就可以很容易的猜到某些 API,简单易懂,使用很方便。不过时常翻文档还是很有必要的。

再看一下菜单组件,使用方式同样非常简单。

<button mat-button [matMenuTriggerFor]="menu">Menu</button>
<mat-menu #menu="matMenu">
  <button mat-menu-item>Item 1</button>
  <button mat-menu-item>Item 2</button>
</mat-menu>

在我更新 ng-zorro-antd 8.x 之后,我发现 zorro 的菜单组件的使用已经和 Angular Material 一样了。可见优秀的设计理念会被广泛借鉴。

Angular Material 的菜单组件可以说非常强大,除了官网提到的功能之外,我们还可以用以下方式实现动态数据加载的多级菜单,比如 ng-matero 的 Top Menu 布局

<a mat-button [routerLink]="['/', menuItem.state]" *ngIf="menuItem.type === 'link'">
  <span>{{menuItem.name}}</span>
  ...
</a>
...
<!-- level 1 -->
<button mat-button *ngIf="menuItem.type === 'sub'" [matMenuTriggerFor]="menulevel1">
  <span>{{menuItem.name}}</span>
  ...
</button>
<mat-menu #menulevel1="matMenu">
  <ng-container *ngFor="let childLvl1 of menuItem.children">

    <a mat-menu-item [routerLink]="['/', menuItem.state, childLvl1.state]"
       *ngIf="childLvl1.type === 'link'">{{childLvl1.name}}</a>
    ...
    <!-- level 2 -->
    <button mat-menu-item *ngIf="childLvl1.type === 'sub'"
            [matMenuTriggerFor]="menulevel2">{{ childLvl1.name }}</button>
    <mat-menu #menulevel2="matMenu">
      <ng-container *ngFor="let childLvl2 of childLvl1.children">

        <a mat-menu-item
           [routerLink]="filterStates(['/', menuItem.state, childLvl1.state, childLvl2.state])"
           *ngIf="childLvl2.type === 'link'">{{childLvl2.name}}</a>
        ...

      </ng-container>
    </mat-menu>
  </ng-container>
</mat-menu>

另外,菜单组件还可以实现 popover 的效果,不过需要做一些特殊处理,如下:

<mat-menu class="menu-form-wrapper" [hasBackdrop]="false">
  <div (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()">
    ...
  </div>
</mat-menu>

最后可以根据自己的需求调整一下样式。

Angular Material 的表格是我见过最特殊的表格,结构简洁,通过定义动态列渲染数据,以下是一个官网例子:

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!--- Note that these columns can be defined in any order.
        The actual rendered columns are set as a property on the row definition" -->

  <!-- Position Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <td mat-cell *matCellDef="let element"> {{element.position}} </td>
  </ng-container>

  <!-- Name Column -->
  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef> Name </th>
    <td mat-cell *matCellDef="let element"> {{element.name}} </td>
  </ng-container>

  <!-- Weight Column -->
  <ng-container matColumnDef="weight">
    <th mat-header-cell *matHeaderCellDef> Weight </th>
    <td mat-cell *matCellDef="let element"> {{element.weight}} </td>
  </ng-container>

  <!-- Symbol Column -->
  <ng-container matColumnDef="symbol">
    <th mat-header-cell *matHeaderCellDef> Symbol </th>
    <td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

相比于 ng-zorro-antd 会暴露全部的 DOM 结构,这种简洁的结构(CDKTable 的结构也是如此)确实让人不适应,甚至有一些担忧,遇到复杂的需求会不会吃瘪。在我写了大量表格需求之后,我可以很肯定地说 Angular Material 的表格足以应对复杂需求(话也不敢说太满😅)。

我很赞同 ng-alain 对 ng-zorro-antd 表格的进一步抽象,熟悉了 ng-alain 编写表格的方式之后,我一直以为 mat-table 略显笨拙。然而仔细研究一下就会发现,mat-table 是在 DOM 层面的抽象,本质是一样的。

mat-table 对表格列宽的首选操控方式是 CSS,起初我对这种方式也存在疑虑,但是在我亲自封装了 ng-zorro-antd 的表格组件之后,我发现一切都很自然。这让我想起前端流行的一句话,“凡事能用 CSS 完成的就不要用 JS”,这也是我不建议大家用 Less 的原因之一。

ng-matero 的表格示例是最简单的业务表格,可以参考其实现方法。

响应式布局

Angular Material 并没有布局组件。但是不用担心,官方出品了一款基于指令布局的神器 flex-layout,它是专门为 Angular 设计的。基于指令的布局方式和 Bootstrap 的栅格布局是两种不同的设计理念。flex-layout 的使用很简单,可以很快上手,熟悉之后你一定会喜欢这种布局方式。

文章篇幅有限,以我浅薄的资历还无法将 Angular Material 的设计之美剖析的面面俱到,但是如果大家通过这篇文章能够更好的了解 Angular Material 或者对 Angular Material 产生了一点兴趣,我也算是做了一件好事。

任何组件库都无法满足所有业务需求,如果你无法在 Angular Material 中找到可用的组件,你可以尝试第三方组件,或者可以将 ng-zorro-antd 按模块单独引入。在此推荐一些优秀的第三方组件。

如果大家喜欢 Angular 或者对 Angular Material 感兴趣,欢迎进群讨论!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK