侧边栏 Sidenav

sidenav 概览

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

Angular Material provides two sets of components designed to add collapsible side content (often navigation, though it can be any content) alongside some primary content. These are the sidenav and drawer components.

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

The sidenav components are designed to add side content to a fullscreen app. To set up a sidenav we use three components: <mat-sidenav-container> which acts as a structural container for our content and sidenav, <mat-sidenav-content> which represents the main content, and <mat-sidenav> which represents the added side content.

Basic sidenav
Please open on Stackblitz to see result

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

The drawer component is designed to add side content to a small section of your app. This is accomplished using the <mat-drawer-container>, <mat-drawer-content>, and <mat-drawer> components, which are analogous to their sidenav equivalents. Rather than adding side content to the app as a whole, these are designed to add side content to a small section of your app. They support almost all of the same features, but do not support fixed positioning.

Basic drawer
Drawer content
Main content

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

Both the main and side content should be placed inside of the <mat-sidenav-container>, content that you don't want to be affected by the sidenav, such as a header or footer, can be placed outside of the container.

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

The side content should be wrapped in a <mat-sidenav> element. The position property can be used to specify which end of the main content to place the side content on. position can be either start or end which places the side content on the left or right respectively in left-to-right languages. If the position is not set, the default value of start will be assumed. A <mat-sidenav-container> can have up to two <mat-sidenav> elements total, but only one for any given side. The <mat-sidenav> must be placed as an immediate child of the <mat-sidenav-container>.

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

The main content should be wrapped in a <mat-sidenav-content>. If no <mat-sidenav-content> is specified for a <mat-sidenav-container>, one will be created implicitly and all of the content inside the <mat-sidenav-container> other than the <mat-sidenav> elements will be placed inside of it.

Implicit main content with two sidenavs
Please open on Stackblitz to see result

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

The following are examples of valid sidenav layouts:

<!-- 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>

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

And these are examples of invalid sidenav layouts:

<!-- 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>

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

These same rules all apply to the drawer components as well.

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

A <mat-sidenav> can be opened or closed using the open(), close() and toggle() methods. Each of these methods returns a Promise<boolean> that will be resolved with true when the sidenav finishes opening or false when it finishes closing.

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

The opened state can also be set via a property binding in the template using the opened property. The property supports 2-way binding.

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

<mat-sidenav> also supports output properties for just open and just close events, The (opened) and (closed) properties respectively.

Sidenav open & close behavior
Please open on Stackblitz to see result

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

All of these properties and methods work on <mat-drawer> as well.

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

The <mat-sidenav> can render in one of three different ways based on the mode property.

模式

Mode

说明

Description

over

侧边栏浮在主内容上方,并用一个背景遮住主内容

Sidenav floats over the primary content, which is covered by a backdrop

push

侧边栏把主内容挤出去,并用一个背景遮住主内容

Sidenav pushes the primary content out of its way, also covering it with a backdrop

side

侧边栏和主内容并排显示,并收缩主内容的宽度,给侧边栏腾出空间

Sidenav appears side-by-side with the main content, shrinking the main content's width to make space for the sidenav.

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

If no mode is specified, over is used by default.

Sidenav with configurable mode
Please open on Stackblitz to see result

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

The over and push sidenav modes show a backdrop by default, while the side mode does not. This can be customized by setting the hasBackdrop property on mat-sidenav-container. Explicitly setting hasBackdrop to true or false will override the default backdrop visibility setting for all sidenavs regadless of mode. Leaving the property unset or setting it to null will use the default backdrop visibility for each mode.

Drawer with explicit backdrop setting

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

<mat-drawer> also supports all of these same modes and options.

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

Clicking on the backdrop or pressing the Esc key will normally close an open sidenav. However, this automatic closing behavior can be disabled by setting the disableClose property on the <mat-sidenav> or <mat-drawer> that you want to disable the behavior for.

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

Custom handling for Esc can be done by adding a keydown listener to the <mat-sidenav>. Custom handling for backdrop clicks can be done via the (backdropClick) output property on <mat-sidenav-container>.

Sidenav with custom escape and backdrop click behavior
Please open on Stackblitz to see result

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

By default, Material will only measure and resize the drawer container in a few key moments (on open, on window resize, on mode change) in order to avoid layout thrashing, however there are cases where this can be problematic. If your app requires for a drawer to change its width while it is open, you can use the autosize option to tell Material to continue measuring it. Note that you should use this option at your own risk, because it could cause performance issues.

Autosize sidenav

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

The <mat-sidenav> and <mat-drawer> will, by default, fit the size of its content. The width can be explicitly set via CSS:

mat-sidenav {
  width: 200px;
}

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

Try to avoid percent based width as resize events are not (yet) supported.

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

For <mat-sidenav> only (not <mat-drawer>) fixed positioning is supported. It can be enabled by setting the fixedInViewport property. Additionally, top and bottom space can be set via the fixedTopGap and fixedBottomGap. These properties accept a pixel value amount of space to add at the top or bottom.

Fixed sidenav
Please open on StackBlitz to see result

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

A sidenav often needs to behave differently on a mobile vs a desktop display. On a desktop, it may make sense to have just the content section scroll. However, on mobile you often want the body to be the element that scrolls; this allows the address bar to auto-hide. The sidenav can be styled with CSS to adjust to either type of device.

Responsive sidenav
Please open on Stackblitz to see result

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

To react to scrolling inside the <mat-sidenav-container>, you can get a hold of the underlying CdkScrollable instance through the MatSidenavContainer.

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

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

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

The <mat-sidenav> and <mat-sidenav-content> should each be given an appropriate role attribute depending on the context in which they are used.

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

For example, a <mat-sidenav> that contains links to other pages might be marked role="navigation", whereas one that contains a table of contents about might be marked as role="directory". If there is no more specific role that describes your sidenav, role="region" is recommended.

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

Similarly, the <mat-sidenav-content> should be given a role based on what it contains. If it represents the primary content of the page, it may make sense to mark it role="main". If no more specific role makes sense, role="region" is again a good fallback.

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

The sidenav has the ability to capture focus. This behavior is turned on for the push and over modes and it is off for side mode. You can change its default behavior by the autoFocus input.

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

By default the first tabbable element will recieve focus upon open. If you want a different element to be focused, you can set the cdkFocusInitial attribute on it.

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

This error is thrown if you have more than one sidenav or drawer in a given container with the same position. The position property defaults to start, so the issue may just be that you forgot to mark the end sidenav with position="end".