美文网首页angular
Angular cdk 学习之 Overlay

Angular cdk 学习之 Overlay

作者: tuacy | 来源:发表于2018-12-15 09:26 被阅读41次

           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)

            位置策略(PositionStrategy):用来确定overlay在页面中的显示位置。overlay里面提供了OverlayPositionBuilder类来构建PositionStrategy。overlay也给我们提供了三种PositionStrategy:GlobalPositionStrategy、ConnectedPositionStrategy和FlexibleConnectedPositionStrategy。

    1.1 GlobalPositionStrategy

           当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;
    }
    

    1.2 ConnectedPositionStrategy

           当overlay的位置需要依赖于另外一个视图的位置的时候采用该ConnectedPositionStrategy来确定overlay的位置。因为ConnectedPositionStrategy完全可以用FlexibleConnectedPositionStrategy来代替。所有我们直接看FlexibleConnectedPositionStrategy。

    1.3 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)

            滑动策略(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是一个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。

    4.1 CdkOverlayOrigin

           添加了CdkOverlayOrigin指令的视图表示该视图是overlay的origin视图。CdkOverlayOrigin指令的使用非常简单没有@Input、@Output。

           Selector: [cdk-overlay-origin] [overlay-origin] [cdkOverlayOrigin]

           Exported as: cdkOverlayOrigin

    4.2 CdkConnectedOverlay

           添加了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<void> @Output() overlay attach的时候回调
    backdropClick: EventEmitter<MouseEvent> @Output() 点击了overlay背景层的回调
    detach: EventEmitter<void> @Output() overlay detach的时候调用
    overlayKeydown: EventEmitter<KeyboardEvent> @Output() overlay显示的时候有键盘按键按下
    positionChange: EventEmitter<ConnectedOverlayPositionChange> @Output() overlay位置改变的时候的回调
    dir: Direction overlay里面的布局方向
    overlayRef: OverlayRef overlay对应的OverlayRef(对overlay的一个封装,类似ElementRef)

    五 Overlay使用

           虽然咱们上面讲了一大堆,然而都是让我们知道什么去使用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 {
    }
    

    5.1 把OverlayPanelComponent组件显示在屏幕中间

           这里主要是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);
            });
        }
    }
    
    

    5.2 把OverlayPanelComponent组件显示在屏幕上,自己控制位置

            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));
        }
    }
    
    

    5.3 overlay里面显示ng-template内容

           怎么在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();
            }
        }
    }
    
    

    5.4 overlay依赖于某个视图(origin)显示

           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();
            }
        }
    }
    
    

    5.5 指令使用

           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

    相关文章

      网友评论

        本文标题:Angular cdk 学习之 Overlay

        本文链接:https://www.haomeiwen.com/subject/hnvohqtx.html