【ネットワーク全体で最もハードコアなReactコンポーネントライブラリチュートリアル】手書き強化版 @popper-js (ロジックコード完全版)

序文

この記事は【現時点で最高のReactコンポーネントライブラリチュートリアル】手書き強化版 @popper-js (メインロジック解析編) の2回目の記事です。

この種の記事は通常、React コンポーネント ライブラリを作成するディープ プレイヤー向けで、オリジナルの @popper-js はフロー型システムを使用していますが、ここでは ts を使用します。メインロジックを理解したい場合は、上記の[Current Best React Component Library Tutorial] 手書き強化版 @popper-js (メインロジック解析) を読めば十分です。

さっそく、@popper-js の拡張バージョンのロジックを書き続けましょう。

レンダリングメインロジック分析

1. 現在の位置情報の表現と位置情報の更新を担当するインスタンスを作成します

まず createPopper メソッドを呼び出し、3 つのパラメータを渡します。

  1. reference: 参照要素、つまりポッパーをトリガーする要素。DOM 要素または DOM 要素を返す関数を指定できます。ポッパー reference は の位置に基づいてポッパーの位置を計算します。
  2. popper: ポッパー要素、つまり配置される要素。DOM 要素または DOM 要素を返す関数を指定することもできます。reference ポッパーはと の関係に従ってポッパーの位置を計算します popper 。
  3. options: 配置位置、上部または下部など、外部から渡されるカスタム パラメーター。

以下に示すように:

画像.png

2. Instance オブジェクトの setOption メソッドは初期化中に呼び出されます。その目的は、オプションをマージすることです。

たとえば、デフォルトのポップアップ位置は基準の下にありますが、外部の位置は基準の上などにカスタマイズできるため、オプションをマージする必要があります。

3. setOption メソッドは、主にポッパー要素の位置の座標を計算するために、位置決めロジックをトリガーします。

  • 配置するときは、後でスクロール バーをスクロールします。次の図に示すように、配置要素は最初は上部にありますが、上部までスクロールするため、位置の調整が必要になる場合があります。画像.png

可以看到下图,由于浏览器滚动到上方,上方预留的空间不够popper显示到reference元素的上方,所以我们popper的位置变为了朝下。

画像.png

所以我们就需要监听所有popper和reference元素的中父元素有滚动条的情况。

此时我们需要找到这些父元素。体现在代码:

  state.scrollParents = {
        reference: isElement(reference) ? listScrollParents(reference as Element) : [],
        popper: listScrollParents(popper),
      };

这里的关键函数是listScrollParents,它的逻辑简述为:

  • 从当前节点开始,查看是否是具有滚动属性,判断方式:
export function isScrollParent(element: Element | HTMLElement): boolean {
  const { overflow, overflowX, overflowY, display } = getComputedStyle(element);
  return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['contents'].includes(display);
}

比如说,你的属性有overflow: auto, overflow: scroll等等,我认为你是可能具有滚动条的,但是为什么源码将overflow:hidden也算进去,就不知道为什么了,了解的同学可以留言区告知。

  • 此时会检查reference和popper是否是html元素,如果不是就退出,并控制台警告,必须是html元素才行。 判断方式主要是查看是否有getBoundingRect方法:
export function areValidElements(...args: Array<any>): boolean {
  return !args.some((element) => !(element && typeof element.getBoundingClientRect === 'function'));
}

  • 接着计算reference元素的位置,也就是popper元素想定位,那么定位到reference元素的左上角的话,坐标是多少,对应代码如下:
      state.rects = {
        reference: getCompositeRect(reference, getOffsetParent(popper)),
        popper: getLayoutRect(popper),
      };

这里的计算方法【目前最好的react组件库教程】手写增强版 @popper-js (主体逻辑分析) 在这篇文章里有详细描述。

  • 接着把计算出来了reference元素的位置经过中间件处理,比如说,我想把popper元素定位到reference元素的上方,那么首先我们说了,上面计算得到reference元素的左上角的坐标赋给popper,但是此时popper的左上角跟reference元素的左上角重合了

如果要放置到reference元素的上方,是不是还要把此时左上角的y坐标再减去popper元素的高度才对,然后x坐标需要:

reference.x + reference.width / 2 - popper.width / 2

至于为什么这样,你可以思考一下,这里你主要明白我的意思即可,要了解详细代码逻辑,具体代码地址在文章开始处。

  • 我们这里的中间件主要有4个

popperOffsets中间件

这里主要是计算坐标,比如说刚才我们假设popper元素要定位到reference元素上方时,如何计算,那么所有位置的坐标换算,都在这个中间件处理。

这里位置示意图如下,共有12个位置:

画像.png

computeStyles中间件

这个中间件有些逻辑有点绕,还是要说明一下

之前我们通过popperOffsets中间件按道理来说已经计算完毕了,但还需要一些补充

  • 假如此时我们popper相当于reference元素是在上方,如下图:

画像.png

此时,我们加入popper的定位方式是:

position: 'absolute',
top: 100px
left: 100px

如果此时我将popper的内容(也就是This is a popup box),变为了两行,会发生什么情况呢?如下图:

画像.png

因为高度变高了,换行的内容遮盖住了reference元素。如何解决呢,computeStyles组件里,我们发现是这种情况,把定位方式变一下:

之前是:

position: 'absolute',
top: 100px
left: 100px

我们改为:

position: 'absolute',
bottom: -600px
left: 100px

我们以bottom为标准,此时再改变高度,就变为:

画像.png

是不是解决方式比较巧妙呢,但是最好的解决方式是什么,监听popper的元素如果有width和height的变化,重新计算定位坐标。

第二个重点是,computeStyles组件使用了css加速,利用transform将我们最终得到的x坐标和y坐标导出来,最终我们会在react层面把这个坐标赋值给popper元素的style属性。

offset中间件

比如,我们想要popper元素在定位后向上移动10px,或者向左移动10px,都可以将参数传入offset中间件,它就是来变换坐标的。

flip中间件

flip是目前遇到逻辑最复杂的中间件,它主要解决的问题是什么呢?

原理是,比如我们现在placement:bottom,表示定位到reference元素的下方,当我们向下滚动的时候,是不是这个定位的元素因为在下方,迟早会到视口的下面,如下图:

画像.png

为了能看见tooltip,我们自动翻转到上方!

画像.png

这就是flip的功能,至于如何实现,我们拿最简单的上面的案例做分析:

上の図のツールチップ要素がスクロール時にブラウザのビューポートを部分的に超えているかどうかを検出する必要があります。超えていることが検出された場合にのみ、ツールチップ要素を反転できます。

検出のアイデア:

  • 最初にスクロールする場所はウィンドウだけでなく、ツールチップ要素の親要素もスクロールできます。このとき、ツールチップ要素の親要素のスクロールにより、スクロール中にツールチップ要素を非表示にすることもできます。以下に続きます:

画像.png

図には 2 つのスクロール バーがあり、いずれか特定の範囲までスクロールすると、青いボタン コンポーネントを非表示にすることができます。

したがって、すべてのスクロールコンテナを収集する必要があります。それらのすべての上の境界線とボタン要素の間の最短距離はどれか、すべての左境界線とボタン要素の間の最短距離はどれか、同じことが下部にも当てはまります。そして右側。

このようにして、ボタン要素のアクティビティの最小範囲が計算され、この範囲内でいつでもスクロールによってボタン要素が非表示になる可能性があります。

この関数は、detectOverflow と呼ばれる @popper-js から抽出され、最終的にオブジェクトを返します。

{
    top: popper元素距离最近的滚动容器上方的距离,正数表示还在可是区域内,
    left: popper元素距离最近的滚动容器左边的距离,,正数表示还在可是区域内,
    right: popper元素距离最近的滚动容器右方的距离,正数表示还在可是区域内,
    bottom: popper元素距离最近的滚动容器下方的距离,,正数表示还在可是区域内,
}

このようにして、例えば今ポッパー要素が一番上にあるべきかどうか、その上、左、右がすべて正の数であるかどうかを確認するだけでよく、負の数であれば、ということになります。この時点では、ポッパー要素は領域の一部を隠しています。

したがって、位置が見つかっている限り、その 3 つの方向はすべて正の値であり、この方向のポッパー要素は完全に可視領域内にあります。

問題は、すべての方向が可視領域内にない場合はどうなるかということです。

そうすると、この時点では関係なく、もともとどちらの方向であっても同じ方向なので、視覚的にはまったく問題ありません。

この記事は終わりました。この記事が悪くないと思われる場合は、私の反応コンポーネント ライブラリ プロジェクト satr へようこそ。まだ継続的に反復中です。このチュートリアルはネットワーク全体で最も難しいコアになると思います。本番環境はコンポーネント ライブラリ プロジェクトに反応します!

おすすめ

転載: juejin.im/post/7264921613544390708