dom-align 源码浅析

1. 前言

一直对于antd的源码耿耿于怀,日常开发中遇到要开发组件总是不清楚怎么去设计接口,抄antd也苦于其组件层层封装且代码分散在各个角落,无从抄起。

在工作间隙之中,粗略的翻看了dom-align的源码,antd 内部许多定位的弹窗类组件都基于它,dom对齐在日常开发中也有需求,遂有了本文。

2. 解读

dom-align 仓库

2.1 API

使用:

import domAlign from 'dom-align';

const alignConfig = {
  points: ['tl', 'tr'], // 将source元素的 `top - center` 与 target 元素的 `top - right` 对齐
  offset: [10, 20],  // `source` 的 x偏移量是10, y的偏移量是 20
  targetOffset: ['30%','40%'], // `target` 的 x 偏移量是30%, y 的偏移量是 40%, 百分比是基于target
  overflow: { adjustX: true, adjustY: true }, // 当 source 被遮挡住的时候,是否进行自动调整
};

domAlign(sourceNode, targetNode, alignConfig);

2.2 source code

dom-align 暴露 {alignElement, alignPoint} 两个方法,默认暴露的是 alignElement 方法

2.2.1 alignElement方法

先暂时只看到 getRegion 部分,看看该方法做了什么:

2.2.2 getRegion方法

如果该元素不是 window 元素或者 document 元素,则去取 该元素的 offset信息,offset 信息即为 元素距离Document即整个文档的左上角的left值和top值

2.2.3 offset方法

offset信息会等于元素的getBoundingClientRect()中返回的left, top加上对应window横向和竖向滚动的距离

由于getBoundingClientRect()返回的只是元素对应 浏览器可视区域width,height,left,top等值,则该left+浏览器横向滚动距离 = 元素距离整个文档最左边的距离

不过,除了这样简单计算之外,其代码中还会考虑到竖向滚动条的宽度:document.documentElement.clientLeft || body.clientLeft

然后offset还会携带元素的width和height信息,整体像是增强版的getBoundingClientRect,不够此时返回的left,top是相对于文档的,而不是相对于可视区域的。

2.2.4 getWHIgnoreDisplay方法

在获取元素的width和height信息时,该库封装了一个getWHIgnoreDisplay方法,看名字即为"获取元素的宽高信息而不管元素的display属性值"

2.2.5 getWHIgnoreDisplay方法

该方法首先判断 elem.offsetWidth是否为0 ,因为当元素display:none的时候,宽度为0.

而当元素display:none的时候,先通过 swap(elem, cssShow),来看下swap的代码:

2.2.6 swap方法

就是当display:none的时候,先让元素display:block,通过visibility在页面上不可见,但是可以获取到该元素的样式信息,之后再将元素还原回display:none.

2.2.7 getWH

该方法封装了获取一个元素各种宽高的方法,将padding,border,margin都考虑在内。

最终 getRegion 方法返回了元素 相对文档的left, top信息,以及width, height 信息。

2.2.8 isOutOfVisibleRect方法

isOutOfVisibleRect方法

先来看getVisibleRectForElement

2.2.9 doAlign方法

该方法为最终的实现dom对齐的方法:

doAlign方法

其中参数依次为 source 元素,目标元素的 width,height,距离文档左上角的left,top值,然后是传入的 align 参数:

先看到这里,即通过知道 source 元素和 target 元素 相对文档 的位置与大小,得到source即将被防止的位置:

getElFuturePos方法

getAlignOffset方法的代码也很简单:

继续回到 doAlign 方法:

上面的代码涉及到isFailXisCompleteFailX 两个方法:

整体看一下 doAlign 方法: 1. 通过计算 source 元素当前位置,根据传入的参数,计算出即将所在的位置 2. 如果参数允许调整x和y方向的值,也即将所在的位置有内容会被遮挡,则进行位置的调整 3. 调整方法为 先反向调整,x方向交换 lr,y方向交换 tb 4. 反转之后检查 1. 如果反转之后完全放不下,啥也不干 2. 反转之后,不是完全放不下,则根据反转之后的位置重新计算将要被放置的位置 5. 判断是否任然放不下(感觉这段代码只适用于 反转之后不是完全放不下的情况,因此此时才会重新计算将要被放置的位置,而确实完全放不下的情况,则这段代码与之前判断 isFail 没啥区别),如果仍然放不下,且确实需要调整,则可能会改变元素的宽度高度了 6. 最后通过 utils.offset 方法设置最终的 left,top 样式

setOffset方法

已知原来相对于文档的left和top,和最终相对文档的left和top值:

  1. setTransform方法,translate的值即为:

  2. setLeftTop方法稍微复杂些,实际上是根据相对于文档的 {left, top} 值。设置元素样式上的left和top值:

(现在有点怀疑这么一长段代码有没有必要,originalStyle直接通过getComputedStyle取不可以吗?嗯,经过测试,发现应该是可以的,和我理解的没差)

3. 结语

通过粗略的分析,对dom-align有了更加清晰的认识,不再是完完全全的黑盒子,之后日常开发中如果需要实现dom对齐的需求也可以视情况引入。

代码中的中文注释对我来说挺友好的,有一些小技巧比如getWHIgnoreDisplay,一些小代码片段getPBMWidth在日常开发中都可以使用上(这是最酷的,总有各种代码片段别人已经实现了且经受住了开源的考验,你理解完之后就可以放心的用了,如果不符合要求也能有把握去修改源码)。

接下来,有空要从dom-align往上去阅读antd的源码了~

Last updated

Was this helpful?