noxi雑記

.NET、Angularまわりの小ネタブログ

Angular CDK Overlayで要素の相対位置でオーバーレイを表示する

Angular CDK Overlay の2本目の記事です。前回は画面に対する絶対位置で表示する方法についてでした。

noxi515.hateblo.jp

今回は特定の要素に対して相対位置でオーバーレイを表示する方法です。



環境

Angular CLI: 9.1.3
Node: 12.16.2
OS: win32 x64

Angular: 9.1.3
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.901.3
@angular-devkit/build-angular     0.901.3
@angular-devkit/build-optimizer   0.901.3
@angular-devkit/build-webpack     0.901.3
@angular-devkit/core              9.1.3
@angular-devkit/schematics        9.1.3
@angular/cdk                      9.2.1
@angular/material                 9.2.1
@ngtools/webpack                  9.1.3
@schematics/angular               9.1.3
@schematics/update                0.901.3
rxjs                              6.5.5
typescript                        3.8.3
webpack                           4.42.0

今回のコードは こちら に Push しています。

オーバーレイで Component を表示する

ConnectedOverlay で要素の相対位置にオーバーレイを表示する

FlexibleConnectedPositionStrategy は指定した要素に対して相対位置でオーバーレイを表示する PositionStrategy です。起点となる要素は FlexibleConnectedPositionStrategyOrigin に定義されている HTMLElementElementRef 、そして { x: number, y: number, width: number, height: number } を指定します。要素と言いつつ自分で作った任意の箱も指定できるみたいですね。

まずは適当に表示してみます。起点となる要素の ElementRef は CdkOverlayOrigin で簡単に取得する事ができます。

<button #origin="cdkOverlayOrigin" cdk-overlay-origin (click)="showOverlay(origin.elementRef)">Show overlay</button>
showOverlay(origin: ElementRef) {
  let positionStrategy: FlexibleConnectedPositionStrategy = this._overlay.position().flexibleConnectedTo(origin);

  const config: OverlayConfig = {
    positionStrategy,

    width: 'auto',
    height: 'auto',
  };
  const overlayRef: OverlayRef = this._overlay.create(config);
  const componentPortal: ComponentPortal<OverlayContentComponent> = new ComponentPortal(OverlayContentComponent);
  const componentRef: ComponentRef<OverlayContentComponent> = overlayRef.attach(componentPortal);
}

これでオーバーレイを表示するために設定しているボタンに対して相対位置で表示できるようになりました。次に相対位置を指定します。
相対位置を指定するには FlexibleConnectedPositionStrategy#withPositions(ConnectedPosition[]) を利用します。引数の ConnectedPosition は相対位置を記したオブジェクトの配列です。 ConnectedPosition を複数渡すと、要素の画面位置とオーバーレイ表示がイイカンジになるように実際に表示に使用される ConnectedPosition が変化します。

ConnectedPosition がもつプロパティです。 originX orignY overlayX overlayX の4つを指定します。

プロパティ名 必須
originX x 相対位置の起点となる要素の X 座標の位置。 start center end
originY x 相対位置の起点となる要素の Y 座標の位置。 top center bottom
overlayX x オーバーレイの起点 X 座標の位置。 start center end
overlayY x オーバーレイの起点 Y 座標の位置。 top center bottom
weight -
offsetX -
offsetY -
panelClass -

まずは起点となるボタンに対して中央下に表示してみます。 ConnectedPosition には { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' } を使用します。

showOverlay(origin: ElementRef) {
  let positionStrategy: FlexibleConnectedPositionStrategy = this._overlay.position().flexibleConnectedTo(origin);
  positionStrategy = positionStrategy
    .withPositions([{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' }]);

  const config: OverlayConfig = {
    positionStrategy,

    width: 'auto',
    height: 'auto',
  };
  const overlayRef: OverlayRef = this._overlay.create(config);
  const componentPortal: ComponentPortal<OverlayContentComponent> = new ComponentPortal(OverlayContentComponent);
  const componentRef: ComponentRef<OverlayContentComponent> = overlayRef.attach(componentPortal);
}

f:id:noxi515:20200426165755p:plain
ボタンの下に表示されるオーバーレイ

続いてボタンの横に表示してみます。ボタンの左側に表示するには ConnectedPosition に { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center' } を指定します。

f:id:noxi515:20200426165954p:plain
ボタンの左側に表示されるオーバーレイ

最後に、複数の ConnectedPosition を指定してみます。 CDK のソースコード中にはこんなコメントがありますので、複数を指定した際はきっとイイカンジに表示してくれるでしょう。

Go through each of the preferred positions looking for a good fit.
If a good fit is found, it will be applied immediately.

ではボタンの下またはボタンの上に表示される ConnectedPosition を指定し、実際の挙動を確認します。指定順はボタン下が1番目、ボタン上が2番目です。

showOverlay(origin: ElementRef) {
  let positionStrategy: FlexibleConnectedPositionStrategy = this._overlay.position().flexibleConnectedTo(origin);
  positionStrategy = positionStrategy.withPositions([
    { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' },
    { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' },
  ]);

  const config: OverlayConfig = {
    positionStrategy,

    width: 'auto',
    height: 'auto',
  };
  const overlayRef: OverlayRef = this._overlay.create(config);
  const componentPortal: ComponentPortal<OverlayContentComponent> = new ComponentPortal(OverlayContentComponent);
  const componentRef: ComponentRef<OverlayContentComponent> = overlayRef.attach(componentPortal);
}

f:id:noxi515:20200426171911p:plain
複数のConnectedPositionを指定して表示

画面の複数の位置にボタンを設置してオーバーレイを表示した結果です。画面上、および中央に設置したボタンからは下に、画面下に設置したボタンからは上にオーバーレイが表示されています。表示されるオーバーレイと余白の大きさから、指定した ConnectedPosition のうち丁度良い物を使用してくれているようです。