Angular Material 的設計之美


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,我會在下文中介紹。

豐富的顏色

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;

靈活的主題定制

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 感興趣,歡迎進群討論!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM