Angular CDK Overlay 弹出覆盖物

为什么使用Overlay?

Overlay中文翻译过来意思是覆盖物,它是Material Design components for Angular中针对弹出动态内容这一场景的封装,功能强大、使用方便,尤其在开发自己的组件库时,可以让你少写许多代码,可以说只要是弹出内容的场景基本都可以使用Overlay.
我们自己的组件库中弹出场景基本都已经使用Overlay,如自定义Select、Cascader、Tree Select、Tooltip、Dialog等,总结最重要的的两点好处:

  1. 让使用者不再进行繁琐的位置计算,而简单通过参数配置就实现内容的定位,而且关于位置的各种情况都有考虑到.
  2. 组件的弹出内容都是用Overlay实现,避免了各自实现的产生的不兼容,如相互遮盖问题.

简单示例 – 连结位置源的弹出

下面通过一个示例代码来展示Overlay的使用,这种弹出场景类似于Tooltip,弹出的overlay内容是基于一个参照的位置源origin元素.

安装并且导入模块

项目中如果没有安装CDK,要先安装

  npm install @angular/cdk
导入OverlayModule
import {OverlayModule} from '@angular/cdk/overlay';

@NgModule({
  imports: [
    OverlayModule,
    // ...
  ]
})
export class AppModule {
}
示例模板内容
<div class="demo-trigger">
  <!--触发位置源-->
  <button mat-raised-button
      cdkOverlayOrigin
      type="button"
      [disabled]="overlayRef"
      (click)="openWithConfig()">Open</button>
</div>

<!--弹出动态内容模板-->
<ng-template #overlay>
  <div class="demo-overlay">
    <div style="overflow: auto;">
      <ul><li *ngFor="let item of itemArray; index as i">{{itemText}} {{i}}</li></ul>
    </div>
  </div>
</ng-template>

除了弹出模板,上面模板中还有一个Open按钮,后面要用到它作为位置源origin

注入Overlay服务

在组件的constructor构造函数中注入Overlay服务,下面代码包括组件的定义

@Component({
  selector: 'overlay-demo',
  templateUrl: 'connected-overlay-demo.html'
})
export class ConnectedOverlayDemo {
  @ViewChild(CdkOverlayOrigin, {static: false}) _overlayOrigin: CdkOverlayOrigin;
  @ViewChild('overlay', {static: false}) overlayTemplate: TemplateRef<any>;
  /**
   * 注入Overlay服务
   */
  constructor(
      public overlay: Overlay) { }  

  openWithConfig() {
  }
}

处理注入服务,上面代码还通过ViewChild取到模板中的两个对象,后面用到的时候再解释.

构建位置策略

首先创建一个位置策略,这里使用的是FlexibleConnectedPositionStrategy策略,先看代码

const positionStrategy = this.overlay.position()
        .flexibleConnectedTo(this._overlayOrigin.elementRef)
        .withPositions([
        {
          originX: 'start',
          originY: 'bottom',
          overlayX: 'start',
          overlayY: '较好',
        }
      ]);  

创建FlexibleConnectedPositionStrategy策略的方法flexibleConnectedTo必须要提供一个位置源参数,这里使用的是
this._overlayOrigin.elementRef,弹出内容的位置是基于这个位置源的,this._overlayOrigin其实就是通过ViewChild取的模板中的Open按钮.

调用创建方法
 this.overlayRef = this.overlay.create({
      positionStrategy, // 位置策略
      scrollStrategy: this.overlay.scrollStrategies.reposition(), // 滚动策略
      direction: this.dir.value, // 可用性方面的设置,不用太关注
      minWidth: 200, // overlay层的最小宽度
      minHeight: 50 // overlay层的最小高度
      hasBackdrop: false // 是否显示遮罩层
    }); 

方法会生成一个OverlayRef类型的对象overlayRef,用overlayRef来管理Overlay。

通过overlayRef附加模板

一切准备就绪后,这里就是需要告诉Overlay弹出层要显示的内容,直接弹出模板,在模板中定义,这里用到的是overlayRef的attach方法,代码如下

 this.overlayRef.attach(new TemplatePortal(this.overlayTemplate, this.viewContainerRef)); 

代码中用到了this.overlayTemplate,通过ViewChild取到的显示弹出内容的模板定义.

注:attach方法用到了CDK里面的Protals,attach方法接收的参数类型其实是TemplatePortal,因为这个说到底是动态创建组件,除此之外它还支持组件类型的ComponentPortal,关于Portals可以参考我前面的文章https://zhuanlan.zhihu.com/p/59719621

通过以上简单的几个步骤就实现动态内容的弹出,效果图如下所示

屏幕录制 2019-06-11 下午10.30.13.2019-06-11 22_38_52.gif

简单示例 – 全局弹出

与上面的完全不同,现在介绍下通过Overlay直接弹出内容在窗口上,不连结任何位置源,非常简单只需要更改下位置策略,看下使用的新位置策略的代码

 const positionStrategy = this.overlay.position()
      .global()
      .height('300px')
      .centerHorizontally()
      .较好('70px'); 

调用global()返回的是全局的位置策略GlobalPositionStrategy,基于浏览器窗口绝对定位的位置策略。
以上代码实现:水平居中,距离顶部70px,效果图如下

屏幕录制 2019-06-11 下午10.44.31.2019-06-11 22_47_32.gif

Overlay 位置策略

Overlay通过OverlayPositionBuilder服务提供了三个方法分别对应三种位置策略,OverlayPositionBuilder通过构造函数注入到了Overlay服务中,前面代码this.overlay.position()返回的就是OverlayPositionBuilder类型的对象

ConnectedPositionStrategy – 连结点位置策略

注:该策略已被弃用,使用FlexibleConnectedPositionStrategy策略代替,但是这里的讨论可以继续,用以说明连接点的位置关系

connectedTo方法返回ConnectedPositionStrategy策略实例,该策略实现基于一个操作源上的位置点到Overlay弹出层的位置点连接关系的位置策略,创建策略的代码如下

  connectedTo(
      elementRef: ElementRef,
      originPos: OriginConnectionPosition,
      overlayPos: OverlayConnectionPosition): ConnectedPositionStrategy {
    return new ConnectedPositionStrategy(
        originPos, overlayPos, elementRef, this._viewportRuler, this._document, this._platform,
        this._overlayContainer);
  } 

参数分别是:

  1. elementRef要连接的元素,一般是触发弹出的元素
  2. originPos 连接元素的位置点
  3. overlayPos overlay的位置点

用图表达它所维系的位置关系

image.png

上图所示的位置点组合只是其中一种情况(左下点 – 左上点),位置配置代码如下

 {
  "originX": "start",
  "originY": "bottom",
  "overlayX": "start",
  "overlayY": "较好"
} 

在x方向上可枚举值定义如下

 export type HorizontalConnectionPos = 'start' | 'center' | 'end'; 

在y方向上可枚举值定义

 export type VerticalConnectionPos = '较好' | 'center' | 'bottom'; 

基于以上枚举值可以实现各种位置组合。

FlexibleConnectedPositionStrategy – 灵活的连接点位置策略

注:现在的源代码ConnectedPositionStrategy策略最终也是通过关联FlexibleConnectedPositionStrategy策略实现的,所以推荐直接使用该策略

通过flexibleConnectedTo方法返回FlexibleConnectedPositionStrategy策略实例,这是Overlay最复杂的一个位置策略,所以能称上Flexible,通过指令方式使用Overlay时就是使用的这个策略,它在位置策略上有更多的控制,特性如下:

  1. withDefaultOffsetXwithDefaultOffsetY设置相对基础位置的偏移。
  2. withPositions参数为ConnectionPositionPair类型的数组,提供多种位置组合,当某一种位置组合的弹出内容超出窗口,就会应用对应其它的位置组合来避免内容不可见。
  3. withFlexibleDimensions 控制Overlay弹出层宽度和高度是否被限制在浏览器窗口内,参数设置为ture时,宽度和高度会自适应到浏览器边界,以滚动条形式展现内容。
  4. 等等位置方面其它可预见的细节的处理

创建策略的代码如下

   /**
   * Creates a flexible position strategy.
   * @param origin Origin relative to which to position the overlay.
   */
  flexibleConnectedTo(origin: FlexibleConnectedPositionStrategyOrigin):
    FlexibleConnectedPositionStrategy {
    return new FlexibleConnectedPositionStrategy(origin, this._viewportRuler, this._document,
        this._platform, this._overlayContainer);
  } 

只有一个origin参数,提供了要连结的位置源元素引用。

GlobalPositionStrategy

全局位置策略,global方法返回GlobalPositionStrategy策略实例,无任何参数,创建策略的代码如下

  /**
   * Creates a global position strategy.
   */
  global(): GlobalPositionStrategy {
    return new GlobalPositionStrategy();
  }

GlobalPositionStrategy提供了全局定位的各种方法,并且可以通过链式的方式调用,如下代码

const strategy = this.overlay
  .position()
  .global()
  .width('500px')
  .height('100px')
  .centerHorizontally()
  .centerVertically();

还有较好leftbottomright方法提供各个方位的绝对定位,参数是在这个方位上的偏移值,如居上10px参数就是'10px',这是这个偏移值会打破水平或者垂直方向上的居中。

PositionStrategy 位置策略接口

位置策略接口定义如下

 import {OverlayReference} from '../overlay-reference';

/** Strategy for setting the position on an overlay. */
export interface PositionStrategy {
  /** 附加位置策略到overlay */
  attach(overlayRef: OverlayReference): void;

  /** 更新overlay element 元素的位置. */
  apply(): void;

  /** 当overlay调用detach时调用 */
  detach?(): void;

  /** Cleans up any DOM modifications made by the position strategy, if necessary. */
  dispose(): void;
}

接口定义了位置策略必须包含的方法签名,这是面向对象编程中的常用的抽象方式。
OverlayRef在实现时只依赖PositionStrategy接口而不具体依赖某一个策略的实现,在创建OverlayRef需要提供一个具体的位置策略的实例(一般是在创建Overlay时配置),如果有需要还可以实现自己的位置策略,实现自己的位置策略只需要实现这个接口并且定义接口签名的具体实现。
符合面向对象的三大特性 封装继承多态,符合五大原则中的单一职责原则开放封闭原则,这种抽象思想非常值得学习

滚动策略

Overlay提供了全局服务ScrollStrategyOptions ,用它提供处理overlay滚动时的处理策略。

NoopScrollStrategy – 不提供任何处理

在滚动不做任何事情,调用scrollStrategiesnoop方法

  noop = () => new NoopScrollStrategy(); 
CloseScrollStrategy – 关闭滚动策略

一旦用户有滚动行为,立即关闭overlay弹层,调用scrollStrategiesclose方法

  close = (config?: CloseScrollStrategyConfig) => new CloseScrollStrategy(this._scrollDispatcher,
      this._ngZone, this._viewportRuler, config) 

可以配置config中的参数threshold,设置一个滚动像素的临界点,只有当滚动距离大于此参数时才会关闭overlay.

BlockScrollStrategy – 阻止滚动策略

该策略会阻止页面级的滚动,调用scrollStrategiesblock方法

   block = () => new BlockScrollStrategy(this._viewportRuler, this._document); 

通过给页面的html标签增加样式cdk-global-scrollblock来阻止页面级别的滚动,样式定义如下

position: fixed;
width: 100%;
overflow-y: scroll; 

RepositionScrollStrategy – 重定位滚动策略

一旦用户有滚动行为,该策略会根据滚动的位置更新弹出层的位置,效果就是弹出层会跟随滚动而滚动,相对于位置源的位置不变,调用scrollStrategiesreposition方法

   reposition = (config?: RepositionScrollStrategyConfig) => new RepositionScrollStrategy(
      this._scrollDispatcher, this._viewportRuler, this._ngZone, config) 

config可以配置两个参数,scrollThrottle参数控制滚动事件触发重新更新位置的抖动频率,autoClose参数配置当滚动事件发生时是否关闭overlay弹层(实现 关闭滚动策略 的功能)

滚动的触发

无论是关闭Overlay还是更新Overlay位置,都需要检测滚动事件的触发,这里要用到CDK提供的处理滚动的服务(cdk/scrolling目录下),主要用到ScrollDispatcher,一个处理全局滚动事件的触发器.
关闭滚动策略 和 重定位滚动策略 都是订阅ScrollDispatcher的scrolled方法返回的流(后面统一叫scrolled流),有几点要明确

  1. 全局页面文档的滚动定会触发scrolled流
  2. overlay是否显示backdrop(遮罩层)对 重定位滚动策略 有影响,显示backdrop会阻止页面局部元素的滚动,对全局页面文档滚动没有影响,所以可以看到的效果是,官方的overlay示例即使显示backdrop层,reposition仍然起作用,但自己在实现overlay跟随滚动的时候可能会失败,因为根本触发不了局部滚动事件。
  3. scrolled流是一个全局的滚动监听,任何注入的CdkScrollable所关联的元素滚动都会触发scrolled流(如果滚动跟overlay没有关系,那重新计算位置也没有影响,计算后还是原来的位置,不过这块感觉有待优化)
实现局部元素滚动Overlay层重定位

因为国内的软件大部分是把整个窗口固定,然后再通过局部元素设置样式overflow:scroll实现内容滚动,这里仅提供思路

  1. 关闭backdrop,至于点击backdrop后overlay关闭,就需自己实现了
  2. 根据滚动区域构造CdkScrollable示例,可以用代码遍历origin元素的可滚动父元素

这块理解起来并没有那么容易,很抽象,又是由overlay 、scroll、position策略、scroll策略组合起来的,现在可以先做了解,需要了解细节时再翻看源代码。

总结

文字首先介绍了使用Overlay的好处,以及在我们的组件库中都有那些组件使用了Overlay,然后通过简单的示例代码带大家了解Overlay的使用,后面又介绍了Overlay的位置策略和滚动策略,希望看到最后的各位有些帮助。
Overlay需要说的内容非常的多,而且整体封装的思想也很值得学习,这里就简单介绍这么多,有任何建议或者疑问欢迎留言讨论。
另外文中的示例基本是在Material Design Components for angular 中针对Overlay的Demo,有需要可以自行clone代码学习

示例运行命令
git clone https://github.com/angular/components
cd components
yarn install // 如果提示没有yarn 需要全局装下yarn,node版本要求10.x
npm run dev-app

文章标题:Angular CDK Overlay 弹出覆盖物,发布者:刘佳,转载请注明出处:https://worktile.com/kb/p/6511

(1)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
刘佳刘佳认证作者
上一篇 2022年3月20日 上午1:17
下一篇 2022年3月20日 上午1:21

相关推荐

  • 什么专业跟编程有关系

    编程与多个专业领域密切相关,1、计算机科学与工程、2、软件工程、3、信息技术、4、计算机信息系统、5、人工智能、6、数据科学、7、网络安全、8、电子工程、9、游戏开发、10、生物信息学。这些领域都紧密地结合了编程知识和技能。以计算机科学与工程为例,这个专业深入研究计算机理论、编程语言、软件开发和硬件…

    2024年4月27日
    300
  • 选择 CRM 系统软件的标准到底是什么

    选择 CRM 系统软件的标准是:1、你需要什么功能;2、它灵活敏捷吗;3、它还能帮助你做什么;4、使用起来有多容易;5、如何处理升级和增强功能。在决定选择一款CRM前要确定两件事:你需要什么和你想要什么。更重要的是确定企业的特殊业务需求。 1、你需要什么功能 在决定选择一款CRM前要确定两件事:你需…

    2023年1月30日
    41800
  • 为什么vscode好多人

    一、高效的开发环境 Visual Studio Code(VSCode)之所以受到广泛欢迎,是因为它提供了高效的开发环境、强大的代码编辑功能、灵活的插件系统、开源免费和跨平台兼容性。 其中,高效的开发环境尤为突出,因为VSCode拥有许多内置特性和用户可自定义的设置,这些都是为了优化开发者的工作流程…

    2024年4月3日
    5700
  • 数控编程做什么

    数控编程主要用于制定机器加工工件的指示程序, 其核心功能包括:1、转化图纸信息为机床指令;2、确定工具路径;3、设定加工参数;4、优化加工流程;5、确保加工质量和精度。 转化图纸信息为机床指令是数控编程的基础,它涉及读取和解析设计者的图纸或CAD模型,将其中的几何形状和尺寸转换成机床能理解和执行的指…

    2024年4月26日
    900
  • Python和Java相比较而言,有什么优势

    Python和Java相比较而言,有什么优势:1、功能;2、生态系统;3、安全性。功能是指,Python言语有许多优势,比如简单易用、扩展性强等等,可是在功能方面与Java相比仍是存在必定的距离,这一点不论是在Web开发,仍是在大数据开发范畴,都有必定的表现。 一、功能 Python言语有许多优势,…

    2023年5月8日
    36400
  • 学编程最重要的是什么

    学习编程最重要的是逻辑思维能力、问题解决能力、持续学习的态度、编码实践、以及对于基础知识的扎实掌握。在这些关键因素中,逻辑思维能力尤其重要,它是编程过程中必不可少的技能。编程不仅仅是一门语言或工具的学习,更是一个解决问题的过程。逻辑思维能力使得程序员能够将复杂问题结构化,采用合理的方法逐步分析并解决…

    2024年4月26日
    800
  • 项目收管理费中标方如何管控

    在项目收取管理费中,标方(指中标的供应商或承包商)如何管控是一个需要专注于提高效率、减少不必要开支,并保持项目质量的关键议题。项目管理费的管控涉及到多个方面,包括但不限于预算管理、合同管理、成本效益分析、供应链管理、风险管理等。合同管理是所有这些环节中尤为关键的一环,因为它直接影响到项目的成本控制和…

    2024年4月11日
    5600
  • tnc系统和TNS系统区别

    本文将对两种不同的数控系统——TNC系统与TNS系统进行详细的对比分析。我们将聚焦在以下四个方面:1、系统简介;2、特性和功能;3、应用领域;4、用户选择建议。在深入了解这两种系统的细节后,读者可以根据自身需求做出更加明智的选择。 1、系统简介 TNC系统是由德国海德汉公司(Heidenhain)开…

    2023年8月3日
    1.5K00
  • 糖果编程什么登录不了

    无法登录糖果编程的原因主要有3个:1、网络问题,2、账户信息错误,3、系统故障。网络问题是最常见的原因之一。在这种情况下,用户可能会遇到页面加载缓慢或者根本无法打开网站的情况。这通常是由于网络信号弱或者网络设置不当所致。解决这一问题,首先应检查网络连接是否正常,如需,重置路由器或者尝试使用不同的网络…

    2024年4月26日
    600
  • 处理xml技术有哪些

    处理xml技术有:1、DOM;2、SAX;3、JDOM;4、DOM4J。DOM是基于XML的树结构来完成解析的,DOM解析XML文档时,会根据读取的文档,构建一个驻留在内存的树结构,然后就可以使用DOMAPI来操作这个结构。 1、DOM DOM是基于XML的树结构来完成解析的,DOM解析XML文档时…

    2023年1月11日
    65500

发表回复

登录后才能评论
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部