wzhudev / blog

:book:
220 stars 14 forks source link

Angular CDK Overlay 源码解析 - 2 #35

Closed wzhudev closed 3 years ago

wzhudev commented 4 years ago

上一篇文章介绍了 GlobalPositionStrategy,由于它相对简单,我们没有讲解具体实现。这篇文章我们来分析更加复杂的 ConnectedOverlayPositionStrategy,该策略要求指定一个 origin 元素(可通过 setOrigin 方法指定)或称原点,在浮层元素显示或者窗口滚动的时候,浮层元素就会以原点为锚点,在页面的可视区域中显示。这种策略在组件库中是非常常用的,ng-zorro-antd 的 select cascader date-picker 等组件都用到了它。

这个策略的难点就在于确定浮层的位置:浮层相对于原点一共有 12 种位置,不同的位置可能导致浮层元素的可视面积不一致,该策略需要从 12 种当中选取符合用户设置的优先级,且能够尽可能多地展示浮层元素内容的那一个。

我们还是从 apply 方法说起,如上篇文章所述,当需要调整浮层元素位置时,这个方法就会被调用。

我们省略掉一些分支情形,描述一下这个方法的主要流程

function apply(): void {
    // 重置浮层元素的样式
    this._clearPanelClasses();
    this._resetOverlayElementStyles();
    this._resetBoundingBoxStyles();

    // 获取视窗、原点元素和浮层元素的位置和大小
    this._viewportRect = this._getNarrowedViewportRect();
    this._originRect = this._getOriginRect();
    this._overlayRect = this._pane.getBoundingClientRect();

    // 为每一个可选的位置计算可视区域大小,每个位置按照优先级有序地排列
    for (let pos of this._preferredPositions) {
        // 计算该位置下,浮层元素相对原点元素的定位点
        let originPoint = this._getOriginPoint(originRect, pos);
        // 计算浮层元素的定位点
        let overlayPoint = this._getOverlayPoint(originPoint, overlayRect, pos);
        // 计算这种情况下浮层元素能够在可视窗口内如何进行展示
        let overlayFit = this._getOverlayFit(overlayPoint, overlayRect, viewportRect, pos);

        if (overlayFit.isCompletelyWithinViewport) {
            // 1. 如果能够完全展示在可视区域当中,直接应用这个定位
        }

        if (this._canFitWithFlexibleDimensions(overlayFit, overlayPoint, viewportRect)) {
            // 2. 如果能够通过调整浮层元素的宽高来让浮层元素可视,那么就记录一下这种定位
        }

        // 3. 最后,如果这个定位的可视区域大于其他的定位方法,则将它作为 fallback
    }

    // 处理分支 2 情形中的定位

    // 如果没有分支情形 2 的定位,那么就应用 fallback 定位
    if (this._canPush) {
      // 如果用户允许偏移,那么就把浮层元素推到可视区域当中
    }

    // 否则直接应用定位
    this._applyPosition(fallback!.position, fallback!.originPoint);
}

在了解了算法主要内容的基础下,我们来深入探究一下一些细节问题:

如何对元素进行定位