Angular CDK Overlay 弹出覆盖物
为什么使用Overlay?
Overlay中文翻译过来意思是覆盖物,它是Material Design components for Angular中针对弹出动态内容这一场景的封装,功能强大、使用方便,尤其在开发自己的组件库时,可以让你少写许多代码,可以说只要是弹出内容的场景基本都可以使用Overlay.
我们自己的组件库中弹出场景基本都已经使用Overlay,如自定义Select、Cascader、Tree Select、Tooltip、Dialog等,总结最重要的的两点好处:
- 让使用者不再进行繁琐的位置计算,而简单通过参数配置就实现内容的定位,而且关于位置的各种情况都有考虑到.
- 组件的弹出内容都是用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: 'top',
}
]);
创建 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
通过以上简单的几个步骤就实现动态内容的弹出,效果图如下所示
简单示例 - 全局弹出
与上面的完全不同,现在介绍下通过Overlay直接弹出内容在窗口上,不连结任何位置源,非常简单只需要更改下位置策略,看下使用的新位置策略的代码
const positionStrategy = this.overlay.position()
.global()
.height('300px')
.centerHorizontally()
.top('70px');
调用 global() 返回的是全局的位置策略 GlobalPositionStrategy ,基于浏览器窗口绝对定位的位置策略。
以上代码实现:水平居中,距离顶部70px,效果图如下
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);
}
参数分别是:
- elementRef要连接的元素,一般是触发弹出的元素
- originPos 连接元素的位置点
- overlayPos overlay的位置点
用图表达它所维系的位置关系
上图所示的位置点组合只是其中一种情况(左下点 - 左上点),位置配置代码如下
{
"originX": "start",
"originY": "bottom",
"overlayX": "start",
"overlayY": "top"
}
在x方向上可枚举值定义如下
export type HorizontalConnectionPos = 'start' | 'center' | 'end';
在y方向上可枚举值定义
export type VerticalConnectionPos = 'top' | 'center' | 'bottom';
基于以上枚举值可以实现各种位置组合。
FlexibleConnectedPositionStrategy - 灵活的连接点位置策略
注:现在的源代码ConnectedPositionStrategy策略最终也是通过关联FlexibleConnectedPositionStrategy策略实现的,所以推荐直接使用该策略
通过 flexibleConnectedTo 方法返回FlexibleConnectedPositionStrategy策略实例,这是Overlay最复杂的一个位置策略,所以能称上Flexible,通过指令方式使用Overlay时就是使用的这个策略,它在位置策略上有更多的控制,特性如下:
- withDefaultOffsetX 、 withDefaultOffsetY 设置相对基础位置的偏移。
- withPositions 参数为ConnectionPositionPair类型的数组,提供多种位置组合,当某一种位置组合的弹出内容超出窗口,就会应用对应其它的位置组合来避免内容不可见。
- withFlexibleDimensions 控制Overlay弹出层宽度和高度是否被限制在浏览器窗口内,参数设置为ture时,宽度和高度会自适应到浏览器边界,以滚动条形式展现内容。
- 等等位置方面其它可预见的细节的处理
创建策略的代码如下
/**
* 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();
还有 top 、 left 、 bottom 、 right 方法提供各个方位的绝对定位,参数是在这个方位上的偏移值,如居上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 - 不提供任何处理
在滚动不做任何事情,调用 scrollStrategies 的 noop 方法
noop = () => new NoopScrollStrategy();
CloseScrollStrategy - 关闭滚动策略
一旦用户有滚动行为,立即关闭overlay弹层,调用 scrollStrategies 的 close 方法
close = (config?: CloseScrollStrategyConfig) => new CloseScrollStrategy(this._scrollDispatcher,
this._ngZone, this._viewportRuler, config)
可以配置config中的参数 threshold ,设置一个滚动像素的临界点,只有当滚动距离大于此参数时才会关闭overlay.
BlockScrollStrategy - 阻止滚动策略
该策略会阻止页面级的滚动,调用 scrollStrategies 的 block 方法
block = () => new BlockScrollStrategy(this._viewportRuler, this._document);
通过给页面的html标签增加样式 cdk-global-scrollblock 来阻止页面级别的滚动,样式定义如下
position: fixed;
width: 100%;
overflow-y: scroll;
RepositionScrollStrategy - 重定位滚动策略
一旦用户有滚动行为,该策略会根据滚动的位置更新弹出层的位置,效果就是弹出层会跟随滚动而滚动,相对于位置源的位置不变,调用 scrollStrategies 的 reposition 方法
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流),有几点要明确
- 全局页面文档的滚动定会触发scrolled流
- overlay是否显示backdrop(遮罩层)对 重定位滚动策略 有影响,显示backdrop会阻止页面局部元素的滚动,对全局页面文档滚动没有影响,所以可以看到的效果是,官方的overlay示例即使显示backdrop层,reposition仍然起作用,但自己在实现overlay跟随滚动的时候可能会失败,因为根本触发不了局部滚动事件。
- scrolled流是一个全局的滚动监听,任何注入的CdkScrollable所关联的元素滚动都会触发scrolled流(如果滚动跟overlay没有关系,那重新计算位置也没有影响,计算后还是原来的位置,不过这块感觉有待优化)
实现局部元素滚动Overlay层重定位
因为国内的软件大部分是把整个窗口固定,然后再通过局部元素设置样式 overflow:scroll 实现内容滚动,这里仅提供思路
- 关闭backdrop,至于点击backdrop后overlay关闭,就需自己实现了
- 根据滚动区域构造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
Worktile官网:www.worktile.com
本文作者:Worktile工程师 杨振兴
文章首发于「Worktile官方博客」,转载请注明来源。
Angular CDK Overlay 弹出覆盖物的更多相关文章
- 使用Angular CDK实现一个Service弹出Toast组件
在Angular中,官方团队在开发Material组件库的同时,顺手做了一套Component dev kit,也就是在Angular世界中大名鼎鼎的CDK,这套工具包提供了非常多的前端开发的通用功能 ...
- Angular 10材质的模态弹出示例和教程
在本教程中,我们将通过示例使用Angular 10材质构建模式弹出窗口. 在这里,我们将研究创建Angular 10项目,安装和设置Angular 10材质,以及创建自定义材质模块文件. 在本教程中, ...
- Android应用之——百度地图最新SDK3.0应用,实现最经常使用的标注覆盖物以及弹出窗覆盖物
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/yanglfree/article/details/33333413 一.概述 最新版的百度地图SDK ...
- angularjs 弹出框 $modal
angularjs 弹出框 $modal 标签: angularjs 2015-11-04 09:50 8664人阅读 评论(1) 收藏 举报 分类: Angularjs(3) $modal只有一 ...
- angularjs 弹出框 $modal 参数(转)
angularjs 弹出框 $modal $modal只有一个方法:open,该方法的属性有: templateUrl:模态窗口的地址 template:用于显示html标签 scope:一个作用 ...
- angularJS配合bootstrap动态加载弹出提示内容
1.bootstrp的弹出提示 bootstrap已经帮我们封装了非常好用的弹出提示Popover. http://v3.bootcss.com/javascript/#popovers 2.自定义p ...
- datePiker弹出框被其他div遮挡
最近在做项目的时候,datePiker弹出框被下面的div给遮挡住了,以前也碰到过这样类似的问题,之前直接在style中添加"z-index:1000".但是现在使用angular ...
- 一步步编写avalon组件01:弹出层组件
avalon2已经稳定下来,是时候教大家如何使用组件这个高级功能了. 组件是我们实现叠积木开发的关键. avalon2实现一个组件非常轻松,并且如何操作这个组件也比以前的avalon2,还是react ...
- jquery 简单弹出层
预定义html代码:没有 所有代码通过js生成和移除. 预定义css .z-popup-overlay{ width:100%; min-height: 100%; height:800px; pos ...
随机推荐
- cas系列-cas server demo搭建(二)
一 部署简述 cas server官方推荐采用overlay方式进行部署,通过替换自定义文件,减少项目文件改动,以简化开发和部署,这个有点类似于项目上直接替换java的class文件,由于和git的搭 ...
- Matlab下imwrite,Uint16的深度图像
Matlab下imwrite,Uint16的深度图像 1. 在Matlab命令窗口输入命令: help imwrite 会有如下解释: If the input array is of class u ...
- Leetcode题目337:打家劫舍 III(树形DP-中等)
题目描述: 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区.这个地区只有一个入口,我们称之为“根”. 除了“根”之外,每栋房子有且只有一个“父“房子与之相连.一番侦察之后,聪明 ...
- flutter 中 List 和 Map 的用法
list集合 在Dart中,数组是List对象,因此大多数人只是将它们称为List.以下是一个简单的Dart的List: 创建一个int类型的list List list = [10, 7, 23]; ...
- <JavaScript> call()、apply()、bind() 的用法
其实是一个很简单的东西,认真看十分钟就从一脸懵B 到完全 理解! 先看明白下面: 例 1 obj.objAge; obj.myFun() // 小张年龄 undefined 例 2 shows() ...
- HTTP的响应协议
响应行介绍,响应状态码 1XX: 客户端请求服务器,但是请求未完成,服务器什么事也没干 2XX: 表示响应成功,代表性的状态码就是200 3XX: 请求重定向,代表性的状态码302 4XX: 客户端发 ...
- 阶段5 3.微服务项目【学成在线】_day03 CMS页面管理开发_12-删除页面-服务端-接口开发
删除页面 api里面定义接口 返回类型是ReponseResult @ApiOperation("删除页面") public ResponseResult delete(Strin ...
- python抽取指定url页面的title方法
python抽取指定url页面的title方法 今天简单使用了一下python的re模块和lxml模块,分别利用的它们提供的正则表达式和xpath来解析页面源码从中提取所需的title,xpath在完 ...
- JavaScript:undefined!=false之解 及==比较的规则
JS中有一个基本概念就是: JavaScript中undefined==null 但undefined!==null undefined与null转换成布尔值都是false 如果按照常规想法,比如下面 ...
- css解决fixed布局不会出现滚动条问题
需求是页面移动到一定高度时,顶部出现固定的导航栏,并导航栏带滚动条. CSS很好实现,但是导航栏飘浮顶部后,滚动条怎么也不显示,搜了一些资料终于解决了,现做下笔记. <div class=&qu ...