cdk Overlay主要用来实现在界面上创建浮动面板,例如弹窗(Dialog),下拉框(select)等等都可以通过cdk Overlay来实现。接下来咱们将对Overlay的使用做一个非常简单的介绍。特别推荐大家直接去看官网,官网是最详细的也是最好的文档https://material.angular.io/cdk/overlay/overview
在上一篇文章中我们大概讲了下cdk Portals的使用,咱们这次要讲的 Overlay内部试下就是在Portals的基础之上做的。OverlayRef(Overlay service里面的create函数创建)就对应着Portals里面的PortalOutlet,Overlay里面我们需要放置的内容就对应着Portals里面的Portal。如果你有兴趣或许你可以先看一看前一篇文章关于cdk Portals使用的介绍。
Overlay的使用也很简单,关键在两个地方:位置策略、滑动策略。一个处理overlay的显示位置,一个处理有滑动的时候overlay的动作。
位置策略(PositionStrategy):用来确定overlay在页面中的显示位置。overlay里面提供了OverlayPositionBuilder类来构建PositionStrategy。overlay也给我们提供了三种PositionStrategy:GlobalPositionStrategy、ConnectedPositionStrategy和FlexibleConnectedPositionStrategy。
当overlay的PositionStrategy设置成GlobalPositionStrategy的时候,overlay的位置是相对整个窗口而言的。
GlobalPositionStrategy 常用方法
export declare class GlobalPositionStrategy implements PositionStrategy {
/**
* overlay距离上边
*/
top(value?: string): this;
/**
* overlay距离左边
*/
left(value?: string): this;
/**
* overlay距离下边
*/
bottom(value?: string): this;
/**
* overlay距离右边
*/
right(value?: string): this;
/**
* overlay宽度
*/
width(value?: string): this;
/**
* overlay 高度
*/
height(value?: string): this;
/**
* 水平居中
*/
centerHorizontally(offset?: string): this;
/**
* 垂直居中
*/
centerVertically(offset?: string): this;
}
当overlay的位置需要依赖于另外一个视图的位置的时候采用该ConnectedPositionStrategy来确定overlay的位置。因为ConnectedPositionStrategy完全可以用FlexibleConnectedPositionStrategy来代替。所有我们直接看FlexibleConnectedPositionStrategy。
overlay的位置依赖于某个视图的位置。
export declare class GlobalPositionStrategy implements PositionStrategy {
...
/**
* 当origin视图位置改变之后,可以调用该函数来重写设置overlay的位置
*/
reapplyLastPosition(): void;
/**
* Sets the list of Scrollable containers that host the origin element so that
* on reposition we can evaluate if it or the overlay has been clipped or outside view. Every
* Scrollable must be an ancestor element of the strategy's origin element.
* 看上面英文的意思是,当origin视图包含在CdkScrollable里面的时候,需要设置。我现在还没搞明白这个函数的使用,等后续看了scrolling 看下
*/
withScrollableContainers(scrollables: CdkScrollable[]): void;
/**
* 设置overlay的位置 (我们要重点解释下为什么是数组,比如有这种情况,你本来想把overlay放在origin视图的下面的,有可能会有这种情况吧
* 比如,放下面已经放不下了,在窗口外面去了,这个时候就会去取数组里面的第二个元素布局了)
* ConnectedPosition其实也很好理解,origin视图的哪个点(originX,originY)和overlay(overlayX, overlayY)重合确定位置
*/
withPositions(positions: ConnectedPosition[]): this;
/**
* 设置overlay相对窗口的margin。确定位置判断的时候会用到
*/
withViewportMargin(margin: number): this;
/**
* 设置是否限制在窗体内,设置为true的时候withScrollableContainers里面的数组就有效果了
*/
withFlexibleDimensions(flexibleDimensions?: boolean): this;
/** Sets whether the overlay can grow after the initial open via flexible width/height. */
withGrowAfterOpen(growAfterOpen?: boolean): this;
/** Sets whether the overlay can be pushed on-screen if none of the provided positions fit. */
withPush(canPush?: boolean): this;
/**
* ScrollStrategy设置RepositionScrollStrategy的时候,
* 如果是true,overlay会一直跟着origin视图。false的时候,overlay滑倒窗口边缘的时候就不会动了
*/
withLockedPosition(isLocked?: boolean): this;
/**
* 设置origin视图(overlay依赖的视图)
*/
setOrigin(origin: ElementRef | HTMLElement): this;
/**
* 默认x的偏移量
*/
withDefaultOffsetX(offset: number): this;
/**
* 默认y偏移量
*/
withDefaultOffsetY(offset: number): this;
...
}
滑动策略(ScrollStrategy):当PositionStrategy是ConnectedPositionStrategy或者FlexibleConnectedPositionStrategy的时候,如果overlay依赖的控件位置改变的时候overlay的位置应该怎么变化。overlay里面通过ScrollStrategyOptions来创建ScrollStrategy。同样overlay里面也给我们提供了四种ScrollStrategy:NoopScrollStrategy、CloseScrollStrategy、BlockScrollStrategy和RepositionScrollStrategy。
ScrollStrategy的使用一般配合PositionStrategy的ConnectedPositionStrategy、FlexibleConnectedPositionStrategy来使用。因为GlobalPositionStrategy的时候很少有scroll的情况。所以文章中提到的origin指的是overlay依赖的那个视图。
ScrollStrategy | 解释 |
---|---|
NoopScrollStrategy | origin滚动的时候,overlay位置不动 |
CloseScrollStrategy | origin位置变动的时候,overlay会自动关掉 |
BlockScrollStrategy | origin的滚动也消失了,直接把origin的滚动干没了 |
RepositionScrollStrategy | overlay会跟着origin位置的变动而变动 |
Overlay是一个Service。Overlay主要用来帮我们干三件事:创建OverlayRef(对应overlay视图,然后我们就可以把自定义的组件或者ng-tempalate里面的内容attach到OverlayRef上。这样就是overlay了)、创建PositionStrategy、创建ScrollStrategy。Overlay主要方法介绍:
export declare class Overlay {
/**
* ScrollStrategyOptions - ScrollStrategy构造
*/
scrollStrategies: ScrollStrategyOptions;
/**
* 构造函数,这个我们不用管,我们只需要知道service怎么用就可以了
*/
constructor(
scrollStrategies: ScrollStrategyOptions, _overlayContainer: OverlayContainer
, _componentFactoryResolver: ComponentFactoryResolver
, _positionBuilder: OverlayPositionBuilder
, _keyboardDispatcher: OverlayKeyboardDispatcher
, _injector: Injector, _ngZone: NgZone
, _document: any, _directionality: Directionality
, _location?: Location | undefined);
/**
* 创建OverlayRef
*/
create(config?: OverlayConfig): OverlayRef;
/**
* OverlayPositionBuilder - PositionStrategy构造器
*/
position(): OverlayPositionBuilder;
}
默认情况下overlay是直接添加在body的第一次层节点下面的,这部分的内容是通过OverlayContainer Service来实现的。有兴趣的可以看下OverlayContainer内部的实现。同时cdk Overlay里面也给提供了FullscreenOverlayContainer Service用来应对可能我们某些组件需要设置全屏的情况,比如有一个播放节点video。有全屏播放的功能。在全屏播放的时候你也想弹出overlay来的。这个时候就得添加providers: [{provide: OverlayContainer, useClass: FullscreenOverlayContainer}]。
cdk Overlay里面给提供了两个指令CdkOverlayOrigin和CdkConnectedOverlay。一个对应origin(overlay定位依赖的视图),一个对应overlay。
添加了CdkOverlayOrigin指令的视图表示该视图是overlay的origin视图。CdkOverlayOrigin指令的使用非常简单没有@Input、@Output。
Selector: [cdk-overlay-origin] [overlay-origin] [cdkOverlayOrigin]
Exported as: cdkOverlayOrigin
添加了CdkConnectedOverlay指令的嵌入视图元素(一般都是加载ng-template上)表明该嵌入元素是一个overlay。
Selector: [cdk-connected-overlay] [connected-overlay] [cdkConnectedOverlay]
Exported as: cdkConnectedOverlay
CdkConnectedOverlay指令提供的属性有
属性 | 类型 | 解释 |
---|---|---|
backdropClass: string | @Input(‘cdkConnectedOverlayBackdropClass’) | 背景层class |
flexibleDimensions: boolean | @Input(‘cdkConnectedOverlayFlexibleDimensions’) | overlay是否需要限制在窗口内 |
growAfterOpen: boolean | @Input(‘cdkConnectedOverlayGrowAfterOpen’) | 覆盖层是否可以在初始打开后增长 |
hasBackdrop: any | @Input(‘cdkConnectedOverlayHasBackdrop’) | 是否给overlay设置背景层 RepositionScrollStrategy的时候overlay是否一致跟着origin走,就算origin滑出到屏幕外面去了也跟出去 |
height: number | string | @Input(‘cdkConnectedOverlayHeight’) | overlay 高度 |
lockPosition: any | @Input(‘cdkConnectedOverlayLockPosition’) | |
minHeight: number | string | @Input(‘cdkConnectedOverlayMinHeight’) | overlay 最小高度 |
minWidth: number | string | @Input(‘cdkConnectedOverlayMinWidth’) | overlay最小宽度 |
offsetX: number | @Input(‘cdkConnectedOverlayOffsetX’) | overlay x偏移 |
offsetY: number | @Input(‘cdkConnectedOverlayOffsetY’) | overlay y偏移 |
open: boolean | @Input(‘cdkConnectedOverlayOpen’) | overlay显示隐藏 |
origin: CdkOverlayOrigin | @Input(‘cdkConnectedOverlayOrigin’) | 设置overlay的origin(依赖的视图) |
panelClass: string | string[] | @Input(‘cdkConnectedOverlayPanelClass’) | overlay添加class |
positions: ConnectedPosition[] | @Input(‘cdkConnectedOverlayPositions’) | overlay位置设定 |
push: boolean | @Input(‘cdkConnectedOverlayPush’) | 如果我们给overlay提供的位置都不适合的时候,是否可以重叠显示 |
scrollStrategy: ScrollStrategy | @Input(‘cdkConnectedOverlayScrollStrategy’) | ScrollStrategy |
viewportMargin: number | @Input(‘cdkConnectedOverlayViewportMargin’) | overlay窗口margin |
width: number | string | @Input(‘cdkConnectedOverlayWidth’) | overlay 宽度 |
attach: EventEmitter | @Output() | overlay attach的时候回调 |
backdropClick: EventEmitter | @Output() | 点击了overlay背景层的回调 |
detach: EventEmitter | @Output() | overlay detach的时候调用 |
overlayKeydown: EventEmitter | @Output() | overlay显示的时候有键盘按键按下 |
positionChange: EventEmitter | @Output() | overlay位置改变的时候的回调 |
dir: Direction | 无 | overlay里面的布局方向 |
overlayRef: OverlayRef | 无 | overlay对应的OverlayRef(对overlay的一个封装,类似ElementRef) |
虽然咱们上面讲了一大堆,然而都是让我们知道什么去使用overlay。下面我们通过几个简单的例子来看下overlay怎么使用。例子里面没有写传递参数的情况,如果有传递参数的情况可以参考上一篇文章 Angular cdk 学习之 Portals
非常重要:使用之前要加上Overlay库里面的css同时导入OverlayModule
添加Overlay库里面的css 文件,推荐在最外层的styles.css里面添加
@import '~@angular/cdk/overlay-prebuilt.css';
导入OverlayModule
import {NgModule} from '@angular/core';
...
import {OverlayModule, OverlayContainer, FullscreenOverlayContainer} from "@angular/cdk/overlay";
...
@NgModule({
imports: [
...
OverlayModule,
...
],
...
})
export class CdkOverlayModule {
}
在实例之前我们先自定义一个非常简单的动态组件,后面的实例我们都会用到这个组件,注意哦这个动态组件除了在declarations里面需要申明,在entryComponents里面也需要申明。代码如下
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-overlay-panel',
template: `
<p class="wu-overlay-pane">Overlay展示</p>
`,
styles: [`
.wu-overlay-pane {
margin: 0;
padding: 10px;
border: 1px solid black;
background-color: skyblue;
}
`]
})
export class OverlayPanelComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
@NgModule({
...
declarations: [
...
OverlayPanelComponent
],
entryComponents: [
OverlayPanelComponent
]
})
export class CdkOverlayModule {
}
这里主要是GlobalPositionStrategy的使用,以及hasBackdrop的使用。代码如下。
import {Component, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {Overlay, OverlayConfig} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
import {OverlayPanelComponent} from './panel/overlay-panel.component';
@Component({
selector: 'app-cdk-overlay',
template: `
<!-- 全局显示 页面中心显示 (点击的时候显示) -->
<button (click)="showOverlayGlobalPanelCenter()">页面中心显示</button>
`,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class CdkOverlayComponent {
constructor(public overlay: Overlay
, public viewContainerRef: ViewContainerRef) {
}
/**
* overlay 在整个屏幕的中间显示
*/
showOverlayGlobalPanelCenter() {
// config: OverlayConfig overlay的配置,配置显示位置,和滑动策略
const config = new OverlayConfig();
config.positionStrategy = this.overlay.position()
.global() // 全局显示
.centerHorizontally() // 水平居中
.centerVertically(); // 垂直居中
config.hasBackdrop = true; // 设置overlay后面有一层背景, 当然你也可以设置backdropClass 来设置这层背景的class
const overlayRef = this.overlay.create(config); // OverlayRef, overlay层
overlayRef.backdropClick().subscribe(() => {
// 点击了backdrop背景
overlayRef.dispose();
});
// OverlayPanelComponent是动态组件
// 创建一个ComponentPortal,attach到OverlayRef,这个时候我们这个overlay层就显示出来了。
overlayRef.attach(new ComponentPortal(OverlayPanelComponent, this.viewContainerRef));
// 监听overlayRef上的键盘按键事件
overlayRef.keydownEvents().subscribe((event: KeyboardEvent) => {
console.log(overlayRef._keydownEventSubscriptions + ' times');
console.log(event);
});
}
}
GlobalPositionStrategy的使用,GlobalPositionStrategy自定义位置。
import {Component, Inject, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {Overlay, OverlayConfig} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
import {OverlayPanelComponent} from './panel/overlay-panel.component';
import {DOCUMENT} from '@angular/common';
@Component({
selector: 'app-cdk-overlay',
template: `
<!-- 全局显示 页面中显示位置自己控制 -->
<button (click)="showOverlayGlobalPanelPosition()">页面中显示,自己控制位置</button>
`,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class CdkOverlayComponent {
globalOverlayPosition = 0;
constructor(public overlay: Overlay
, public viewContainerRef: ViewContainerRef
, @Inject(DOCUMENT) public _document: any) {
}
/**
* overlay 在整个屏幕位置,自己控制位置
*/
showOverlayGlobalPanelPosition() {
const config = new OverlayConfig();
config.positionStrategy = this.overlay.position()
.global()
.left(`${this.globalOverlayPosition}px`) // 自己控制位置
.top(`${this.globalOverlayPosition}px`);
this.globalOverlayPosition += 30;
config.hasBackdrop = true;
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose(); // 点击背景关掉弹窗
});
overlayRef.attach(new ComponentPortal(OverlayPanelComponent, this.viewContainerRef));
}
}
怎么在overlay上显示ng-template里面的内容。
import {Component, ViewChild, ViewEncapsulation} from '@angular/core';
import {Overlay, OverlayConfig, OverlayRef} from '@angular/cdk/overlay';
import {TemplatePortalDirective} from '@angular/cdk/portal';
@Component({
selector: 'app-cdk-overlay',
template: `
<!-- 鼠标移入的时候显示 ng-template对应的内容,移出的时候不显示 -->
<button style="margin-left: 10px" (mouseenter)="showOverlayPanelTemplate()"
(mouseleave)="dismissOverlayPanelTemplate()">
显示 ng-template 内容
</button>
<!-- ng-template overlay 将要显示的内容 -->
<ng-template cdk-portal #overlayGlobalTemplate="cdkPortal">
<p class="template-overlay-pane"> ng-temtortelliniTemplateplate显示 </p>
</ng-template>
`,
styles: [`
.template-overlay-pane {
padding: 10px;
border: 1px solid black;
background-color: skyblue;
}`],
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class CdkOverlayComponent {
globalOverlayPosition = 0;
private _overlayTemplateRef: OverlayRef;
@ViewChild('overlayGlobalTemplate') templateGlobalPortals: TemplatePortalDirective;
constructor(public overlay: Overlay) {
}
/**
* 显示 ng-template 的内容
*/
showOverlayPanelTemplate() {
const config = new OverlayConfig();
config.positionStrategy = this.overlay.position()
.global()
.centerHorizontally()
.top(`${this.globalOverlayPosition}px`);
this.globalOverlayPosition += 30;
this._overlayTemplateRef = this.overlay.create(config);
this._overlayTemplateRef.attach(this.templateGlobalPortals);
}
/**
* 移除 ng-template 内容
*/
dismissOverlayPanelTemplate() {
if (this._overlayTemplateRef && this._overlayTemplateRef.hasAttached()) {
this._overlayTemplateRef.dispose();
}
}
}
FlexibleConnectedPositionStrategy的使用,怎么通过FlexibleConnectedPositionStrategy来控制显示的位置(ConnectedPosition)。同时也有ScrollStrategy的使用
import {Component, ElementRef, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {Overlay, OverlayConfig, OverlayRef} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
import {OverlayPanelComponent} from './panel/overlay-panel.component';
@Component({
selector: 'app-cdk-overlay',
template: `
<!-- 依附某个组件或者template显示,鼠标移入的时候显示,移出来的时候不显示 -->
<button style="margin-left: 10px" #connectComponentOrigin
(mouseenter)="showOverlayPanelConnectComponent()"
(mouseleave)="dismissOverlayPanelConnectComponent()">
overlay connect 组件显示
</button>
`,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class CdkOverlayComponent {
private _overlayConnectRef: OverlayRef;
@ViewChild('connectComponentOrigin') _overlayConnectComponentOrigin: ElementRef;
constructor(public overlay: Overlay
, public viewContainerRef: ViewContainerRef) {
}
/**
* overlay connect origin 显示,依附某个组件显示
*/
showOverlayPanelConnectComponent() {
const strategy = this.overlay.position()
.flexibleConnectedTo(this._overlayConnectComponentOrigin.nativeElement)
.withPositions([{
originX: 'center',
originY: 'bottom',
overlayX: 'center',
overlayY: 'top',
offsetX: 0,
offsetY: 0
}]); // 这么理解 origin 组件(依附空组件) 的那个点(originX, originY) 和 overlay组件的点(overlayX, overlayY)
// 重合,从而确定overlay组件显示的位置
strategy.withLockedPosition(true);
const config = new OverlayConfig({positionStrategy: strategy});
config.scrollStrategy = this.overlay.scrollStrategies.reposition(); // 更随滑动的策略
this._overlayConnectRef = this.overlay.create(config);
this._overlayConnectRef.attach(new ComponentPortal(OverlayPanelComponent, this.viewContainerRef));
}
dismissOverlayPanelConnectComponent() {
if (this._overlayConnectRef && this._overlayConnectRef.hasAttached()) {
this._overlayConnectRef.dispose();
}
}
}
cdk-overlay-origin和cdk-connected-overlay怎么配合起来使用,怎么把两个指令关联起来。
import {Component, ViewEncapsulation} from '@angular/core';
import {Overlay} from '@angular/cdk/overlay';
@Component({
selector: 'app-cdk-overlay',
template: `
<button cdk-overlay-origin #trigger="cdkOverlayOrigin" (click)="isMenuOpen = !isMenuOpen">
指令实现
</button>
<ng-template cdk-connected-overlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayWidth]="500"
cdkConnectedOverlayHasBackdrop
[cdkConnectedOverlayOpen]="isMenuOpen"
(backdropClick)="isMenuOpen=false">
<div class="menu-wrap">
我是通过指令实现的Overlay
</div>
</ng-template>
`,
styleUrls: ['./cdk-overlay.component.less'],
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
export class CdkOverlayComponent {
/**
* overlay是否显示
*/
isMenuOpen = false;
constructor(public overlay: Overlay) {
}
}
关于cdk Overlay里面的内容咱们就先将这么多。里面还有很多高级的用法是我们没有讲到的。等待大伙去发掘。如果大家有疑问也可以留言。文章里面涉及的代码在都可以找到https://github.com/tuacy/angular-cdk-study