Angular Material 提供了两组组件,用以给主要内容添加一些可折叠的附属内容(通常是导航,但也可以是任何内容)。它们就是侧边栏(sidenav)和抽屉(drawer)组件。

侧边栏组件旨在为全屏应用添加附属内容。要建立侧边栏,我们需要用到三个组件:<mat-sidenav-container> 用来为主要内容和侧边栏提供一个结构容器;<mat-sidenav-content> 用来表示主要内容,而 <mat-sidenav> 用于表示附属内容。

抽屉组件旨在给应用中的一小部分添加附属内容。这可以使用 <mat-drawer-container><mat-drawer-content><mat-drawer> 来实现,它们分别是各个侧边栏组件的等价物。侧边栏会把应用的附属内容作为整体添加进来,而抽屉只在为应用中的一小部分添加附属内容。 它们所支持的大部分特性都一样,但抽屉不支持固定定位方式。

无论主内容还是附属内容,都应该放在 <mat-sidenav-container> 的内部,而那些你不希望被侧边栏影响到的内容(比如头或脚),可以放在该容器的外部。

附属内容应该包装在 <mat-sidenav> 元素中。它的 position 属性可以指定主内容该放在附属内容的哪一端,它可以是 startend,在从左到右书写的语言中下,分别表示把主内容放在附属内容的左侧或右侧。 如果没有指定 position,则其默认值是 start<mat-sidenav-container> 最多可以拥有两个 <mat-sidenav> 元素,但每一侧只能有一个。 <mat-sidenav> 必须作为 <mat-sidenav-container> 的直属子节点出现。

主要内容应该包裹在 <mat-sidenav-content> 中,如果没有为 <mat-sidenav-container> 指定 <mat-sidenav-content>,则会隐式创建一个,并把 <mat-sidenav-container> 中除了 <mat-sidenav> 元素之外的内容都放进去。

下面是正确使用侧边栏布局的例子:

<!-- Creates a layout with a left-positioned sidenav and explicit content. -->
<mat-sidenav-container>
  <mat-sidenav>Start</mat-sidenav>
  <mat-sidenav-content>Main</mat-sidenav-content>
</mat-sidenav-container>
<!-- Creates a layout with a left and right sidenav and implicit content. -->
<mat-sidenav-container>
  <mat-sidenav>Start</mat-sidenav>
  <mat-sidenav position="end">End</mat-sidenav>
  <section>Main</section>
</mat-sidenav-container>
<!-- Creates an empty sidenav container with no sidenavs and implicit empty content. -->
<mat-sidenav-container></mat-sidenav-container>

下面是错误使用侧边栏布局的例子:

<!-- Invalid because there are two `start` position sidenavs. -->
<mat-sidenav-container>
  <mat-sidenav>Start</mat-sidenav>
  <mat-sidenav position="start">Start 2</mat-sidenav>
</mat-sidenav-container>
<!-- Invalid because there are multiple `<mat-sidenav-content>` elements. -->
<mat-sidenav-container>
  <mat-sidenav-content>Main</mat-sidenav-content>
  <mat-sidenav-content>Main 2</mat-sidenav-content>
</mat-sidenav-container>
<!-- Invalid because the `<mat-sidenav>` is outside of the `<mat-sidenav-container>`. -->
<mat-sidenav-container></mat-sidenav-container>
<mat-sidenav></mat-sidenav>

这些规则也同样适用于抽屉组件。

<mat-sidenav> 可以使用 open()close()toggle() 方法来打开或关闭。 它们都会返回一个 Promise<boolean>,当侧边栏打开之后它会解析为 true,关闭之后解析为 false

这些打开状态也可以在模板中使用 opened 属性进行设置。该属性支持双向绑定。

<mat-sidenav> 也支持一些输出属性:(opened) 表示刚刚打开,(closed) 表示刚刚关闭。

所有这些属性和方法也同样可用在 <mat-drawer> 上。

<mat-sidenav> 可以根据其 mode 属性的值以三种方式之一进行渲染。

模式 描述
over 侧边栏浮动在主要内容之上,主要内容被遮罩覆盖
push 侧边栏将主要内容推开,同时用遮罩覆盖它
side 侧边栏与主内容并排显示,缩减主内容的宽度为侧边栏腾出空间。

如果没有指定 mode,则默认为 over

侧边栏的 overpush 模式默认会显示一个背景,但 side 模式不会。这可以通过 mat-sidenav-container 上的 hasBackdrop 属性进行设置。显式把 hasBackdrop 设置为 truefalse 将会为侧边栏改写默认的背景可见性,而不管处在什么模式下。不设置该属性或把它设置为 null 将会使用每种模式下默认的背景可见性。

<mat-drawer> 也同样支持这些模式和选项。

点击背景或按下 Esc 键通常会关闭侧边栏。 不过,可以通过设置 <mat-sidenav><mat-drawer> 上的 disableClose 属性来禁用这种自动关闭的行为。

可以通过给 <mat-sidenav> 添加 keydown 监听器来定制 Esc 处理器。 可以通过 <mat-sidenav-container> 的输出属性 (backdropClick) 来定制点击背景的处理器。

默认情况下,Material 只会在一些关键时刻(打开、窗口调整大小、模式改变)测量和调整容器的大小,以避免布局颠簸。 但是在某些情况下这可能会有问题。如果你希望在打开抽屉时更改其宽度,可以使用 autosize 选项来告诉 Material 继续测量它。 注意,使用该选项时应该风险自担,因为它可能会导致性能问题。

默认情况下,<mat-sidenav><mat-drawer> 应该自适应其内容的尺寸。不过也可以通过 CSS 来显式指定宽度:

mat-sidenav {
  width: 200px;
}

避免使用基于百分比的宽度,因为 resize 事件尚未支持它。

<mat-sidenav> 只支持固定定位方式(<mat-drawer> 不限)。它可以通过设置 fixedInViewport 属性进行启用。 另外,还可以通过 fixedTopGapfixedBottomGap 来设置顶部和底部的空白。这些属性可以接受一个像素值来指定要加到顶部或底部的空白尺寸。

侧边栏通常要在移动端和桌面端提供不同的行为。在桌面端,只允许内容区滚动是合理的;在移动端,你通常会希望滚动整个 body,这样就能让浏览器自动隐藏地址栏。侧边栏可以使用 CSS 来设置样式,以针对不同类型的设备进行调整。

要响应 <mat-sidenav-container> 内部的滚动事件,你可以通过 MatSidenavContainer 来获取一个底层的 CdkScrollable 实例。

class YourComponent implements AfterViewInit {
  @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer;

  ngAfterViewInit() {
    this.sidenavContainer.scrollable.elementScrolled().subscribe(() => /* react to scrolling */);
  }
}

<mat-sidenav><mat-sidenav-content> 都应该根据它们的上下文给出一个合适的 role 属性。

比如,包含到其它页面的链接的 <mat-sidenav> 可以标记为 role="navigation",而包含目录的则应该标记为 role="directory"。 如果没有什么特别的角色来描述这个侧边栏,建议使用 role="region"

同样,<mat-sidenav-content> 也应该基于其包含的内容来指定角色。如果它表示页面的主要内容,就应该标记为 role="main"。 如果没办法指定合理的角色,同样可以用 role="region" 作为回退值。

sidenav 能够捕获焦点。此功能在 pushover 模式下开启,在 side 模式下则被关闭。你可以通过输入属性 autoFocus 来改变其默认行为。

默认情况下,一旦打开,其中的第一个可接收焦点的元素就会收到焦点。如果你想让另一个元素获得焦点,可以在它上面添加 cdkFocusInitial 属性。

如果在指定容器的同一个 position 有多个侧边栏或抽屉,就会抛出本错误。 由于 position 属性默认为 start,所以出现该问题可能只是因为你忘了给 end 侧边栏标记上 position="end"