如何在Overlay Origin元素高度变化后重新定位已打开的CDK Overlay?
问题:Angular Material CDK Overlay Origin元素高度变化时,如何重新调整Overlay的startY位置?
我正在使用Angular Material CDK Overlay开发一个多选下拉组件,代码如下(取自Angular Material Select组件):
<div class="multi-select-dd"> <label class="multi-select-dd-text">{{ label }}</label> <div class="multi-select-dd-text-container"> <ul class="taggle_list" cdk-overlay-origin (click)="toggle()" #origin="cdkOverlayOrigin" #trigger> <li> <input type="text" class="taggle_input" tabindex="1" style="padding-left: 0px; padding-right: 0px;" autocomplete="off"> </li> </ul> </div> </div> <ng-template cdk-connected-overlay cdkConnectedOverlayHasBackdrop cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop" [cdkConnectedOverlayOrigin]="origin" [cdkConnectedOverlayOpen]="panelOpen" [cdkConnectedOverlayPositions]="_positions" [cdkConnectedOverlayMinWidth]="_triggerRect?.width" [cdkConnectedOverlayOffsetY]="_offsetY" (backdropClick)="close()" (attach)="_onAttached()" (detach)="close()"> <div style="background-color: lightgreen;"> <ul> <li>One</li> <li>Two</li> <li>Three</li> <li>Four</li> <li>Five</li> </ul> </div> </ng-template>
当下拉列表中的选项被选中时,会以芯片形式显示在作为连接位置的容器中,这会改变Origin元素的高度。请问:当Overlay Origin元素高度变化时,是否有标准方法重新调整Overlay的startY位置?
回答
好问题!当Origin元素(也就是承载芯片的容器)高度变化时,CDK Overlay默认不会自动实时更新位置,但确实有几种标准且可靠的方法来解决这个问题,下面我逐一说明:
方法1:手动调用OverlayRef的updatePosition()方法
CDK Overlay提供的OverlayRef对象有一个updatePosition()方法,它会强制重新计算Overlay的位置,适配Origin元素的最新尺寸。
步骤:
- 首先在模板的
cdk-connected-overlay上添加overlayAttached事件,获取OverlayRef实例:
<ng-template cdk-connected-overlay ... (overlayAttached)="onOverlayAttached($event)" > <!-- 下拉内容 --> </ng-template>
- 在组件类中保存
OverlayRef,并编写触发位置更新的方法:
import { OverlayRef } from '@angular/cdk/overlay'; import { Component, ViewChild } from '@angular/core'; @Component({ // 组件元数据 }) export class MultiSelectComponent { private _overlayRef: OverlayRef; @ViewChild('trigger') trigger: any; // 关联模板中的trigger元素 // 保存OverlayRef实例 onOverlayAttached(overlayRef: OverlayRef) { this._overlayRef = overlayRef; } // 手动触发位置更新的方法 private adjustOverlayPosition() { if (this._overlayRef && this._overlayRef.hasAttached()) { this._overlayRef.updatePosition(); // 同步更新trigger的尺寸信息,保证minWidth适配最新状态 this._triggerRect = this.trigger.nativeElement.getBoundingClientRect(); } } // 当添加/移除芯片时,调用这个方法 onChipAddedOrRemoved() { // 先处理芯片的添加/移除逻辑 // ... // 然后触发位置调整 this.adjustOverlayPosition(); } }
方法2:用ResizeObserver自动监听Origin尺寸变化
如果想要更自动化的方式,可以使用浏览器原生的ResizeObserver来监听Origin元素的尺寸变化,一旦高度改变就自动触发位置更新。
组件类中的实现:
import { OverlayRef } from '@angular/cdk/overlay'; import { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; import { Subject, takeUntil } from 'rxjs'; @Component({ // 组件元数据 }) export class MultiSelectComponent implements AfterViewInit, OnDestroy { private _overlayRef: OverlayRef; @ViewChild('origin') origin: any; private destroy$ = new Subject<void>(); ngAfterViewInit() { const originElement = this.origin.elementRef.nativeElement; // 创建ResizeObserver监听元素尺寸变化 const resizeObserver = new ResizeObserver(() => { this.adjustOverlayPosition(); }); resizeObserver.observe(originElement); // 组件销毁时取消监听,避免内存泄漏 this.destroy$.pipe(takeUntil(this.destroy$)).subscribe(() => { resizeObserver.unobserve(originElement); }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } private adjustOverlayPosition() { if (this._overlayRef && this._overlayRef.hasAttached()) { this._overlayRef.updatePosition(); this._triggerRect = this.trigger.nativeElement.getBoundingClientRect(); } } onOverlayAttached(overlayRef: OverlayRef) { this._overlayRef = overlayRef; } }
补充说明
- 为什么默认不自动更新?因为DOM变化后,CDK Overlay不会主动监听所有可能的尺寸变化(出于性能优化考虑),所以需要我们手动触发或者通过观察者来触发位置重新计算。
- 同步更新
_triggerRect:因为你模板中绑定了[cdkConnectedOverlayMinWidth]="_triggerRect?.width",所以在调整位置时最好重新计算这个矩形,保证Overlay的宽度也能适配Origin的最新尺寸。
内容的提问来源于stack exchange,提问作者Vivek Kumar
相关产品推荐
相关产品推荐

