Dom-Align

聊聊 Dom-Align 这个包

最近在写一个弹框的 service,用到了 Dom-Align,来挨着某个元素来弹框,遇到了些坑,记录一下,以免下次再掉进去。
先聊聊 Dom-Align 怎么干活的。这个有个约定, target 指的是参考系,source(目标元素)指的是需要被定位的元素。

  • 我们一般直接调用alignElement(el,refNode,align)这个方法。第一个参数是目标元素,第二个是参考系元素,第三个是 AlignType. 这个方法里面先调用const refNodeRegion = getRegion(target);来获取参考系的可视区域。它是向对于 document 而言的,所以如果 document 有 scrollbar, 这个距离也是计算再内的,数据结构如下:
{
    left:number,
    top:number,
    width:number,
    height:number,
}
  • 在这个函数里面,会调用doAlign(el, refNodeRegion, align, isTargetNotOutOfVisible);
  • doAlign这个方法,跳过一些不重要的,就关注主要的,它会先计算 source的可视区域。const visibleRect = getVisibleRectForElement(source, alwaysByViewport);
  • 然后计算出source将要被放置的区域。
// 当前节点将要被放置的位置
// 这个函数主要是计算出最终的left,top的值。
let elFuturePos = getElFuturePos(
  elRegion,
  tgtRegion,
  points,
  offset,
  targetOffset
);
// 当前节点将要所处的区域
let newElRegion = utils.merge(elRegion, elFuturePos);
  • 下面就是微调,在adjustX,adjustY打开的情况下,如果source溢出,则会考虑在左右,上下进行颠倒来进行调整。
// 如果可视区域不能完全放置当前节点时允许调整
if (
  visibleRect &&
  (overflow.adjustX || overflow.adjustY) &&
  isTgtRegionVisible
) {
  if (overflow.adjustX) {
    // 如果横向不能放下
    if (isFailX(elFuturePos, elRegion, visibleRect)) {
      // 对齐位置反下
      const newPoints = flip(points, /[lr]/gi, {
        l: "r",
        r: "l",
      });
      // 偏移量也反下
      const newOffset = flipOffset(offset, 0);
      const newTargetOffset = flipOffset(targetOffset, 0);
      const newElFuturePos = getElFuturePos(
        elRegion,
        tgtRegion,
        newPoints,
        newOffset,
        newTargetOffset
      );

      if (!isCompleteFailX(newElFuturePos, elRegion, visibleRect)) {
        fail = 1;
        points = newPoints;
        offset = newOffset;
        targetOffset = newTargetOffset;
      }
    }
  }

  if (overflow.adjustY) {
    // 如果纵向不能放下
    if (isFailY(elFuturePos, elRegion, visibleRect)) {
      // 对齐位置反下
      const newPoints = flip(points, /[tb]/gi, {
        t: "b",
        b: "t",
      });
      // 偏移量也反下
      const newOffset = flipOffset(offset, 1);
      const newTargetOffset = flipOffset(targetOffset, 1);
      const newElFuturePos = getElFuturePos(
        elRegion,
        tgtRegion,
        newPoints,
        newOffset,
        newTargetOffset
      );

      if (!isCompleteFailY(newElFuturePos, elRegion, visibleRect)) {
        fail = 1;
        points = newPoints;
        offset = newOffset;
        targetOffset = newTargetOffset;
      }
    }
  }

  // 如果失败,重新计算当前节点将要被放置的位置
  if (fail) {
    elFuturePos = getElFuturePos(
      elRegion,
      tgtRegion,
      points,
      offset,
      targetOffset
    );
    utils.mix(newElRegion, elFuturePos);
  }
  const isStillFailX = isFailX(elFuturePos, elRegion, visibleRect);
  const isStillFailY = isFailY(elFuturePos, elRegion, visibleRect);
  // 检查反下后的位置是否可以放下了,如果仍然放不下:
  // 1. 复原修改过的定位参数
  if (isStillFailX || isStillFailY) {
    let newPoints = points;

    // 重置对应部分的翻转逻辑
    if (isStillFailX) {
      newPoints = flip(points, /[lr]/gi, {
        l: "r",
        r: "l",
      });
    }
    if (isStillFailY) {
      newPoints = flip(points, /[tb]/gi, {
        t: "b",
        b: "t",
      });
    }

    points = newPoints;

    offset = align.offset || [0, 0];
    targetOffset = align.targetOffset || [0, 0];
  }
  // 2. 只有指定了可以调整当前方向才调整
  newOverflowCfg.adjustX = overflow.adjustX && isStillFailX;
  newOverflowCfg.adjustY = overflow.adjustY && isStillFailY;

  // 确实要调整,甚至可能会调整高度宽度
  if (newOverflowCfg.adjustX || newOverflowCfg.adjustY) {
    newElRegion = adjustForViewport(
      elFuturePos,
      elRegion,
      visibleRect,
      newOverflowCfg
    );
  }
}
  • 然后就是更新宽高,如果有变化,source的位置信息被更新。同时返回位置信息。

这个里面用到了很多 Dom 的方法,例如计算可视区域,getVisibleRectForElement,它会递归的找 offsetParent,对于 overflow 不是 visible 的,都会叠加那个可视区域。可视区域是采用{left,top,bottom,right}来描述的,跟 css 里面的 clip 计算是一样的,都是相对于 viewport 的 left,top 来计算的,这样就有了一个矩形区域。在刚才提到的递归的过程,这个矩形区域是越来越小的,其实找的是他们的交集。

上一篇:记canvas画笔笔迹的多次优化过程


下一篇:Leetcode 435 无重叠区间 & Leetcode 452 用最少数量的箭引爆气球 贪心 动态规划