[El tutorial de biblioteca de componentes de reacción más duro de toda la red] versión mejorada escrita a mano @ popper-js (versión completa del código lógico)

prefacio

Este artículo es el segundo artículo de [Tutorial actual de la biblioteca de componentes de Best React] Versión mejorada escrita a mano @popper-js (Análisis de lógica principal) .

Este tipo de artículo es generalmente para jugadores profundos que crean bibliotecas de componentes de reacción. El @popper-js original usa el sistema de tipo de flujo, y yo uso ts aquí. Si desea comprender la lógica principal, basta con leer la versión mejorada manuscrita anterior [Tutorial actual de la biblioteca de componentes de Best React] @popper-js (análisis de la lógica principal) .

Sin más preámbulos, sigamos escribiendo la lógica de la versión mejorada de @popper-js.

Render análisis de lógica principal

1. Cree una instancia responsable de representar la información de ubicación actual y actualizar la información de ubicación

Primero llame al método createPopper y pase 3 parámetros

  1. reference: El elemento de referencia, es decir, el elemento que activa el popper. Puede ser un elemento DOM o una función que devuelva un elemento DOM. Popper  reference calculará la posición del popper en función de la posición de .
  2. popper: El elemento popper, es decir, el elemento a posicionar. También puede ser un elemento DOM o una función que devuelve un elemento DOM. Popper calculará la posición de popper según  la relación entre reference y  popper .
  3. options: Parámetros personalizados transmitidos desde el mundo exterior, como la posición de posicionamiento, superior o inferior.

Como se muestra abajo:

imagen.png

2. El método setOption del objeto Instancia se llama durante la inicialización, el propósito es fusionar opciones

Por ejemplo, nuestra posición emergente predeterminada está debajo de la referencia, pero la posición externa se puede personalizar, como arriba de la referencia, por lo que las opciones deben fusionarse.

3. El método setOption activa la lógica de posicionamiento, principalmente para calcular las coordenadas del posicionamiento del elemento popper.

  • Al posicionar, desplazaremos la barra de desplazamiento más adelante. El elemento de posicionamiento está inicialmente en la parte superior, y es posible que sea necesario ajustar la posición debido al desplazamiento hacia la parte superior, como se muestra en la siguiente figura:imagen.png

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

imagen.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个位置:

imagen.png

computeStyles中间件

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

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

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

imagen.png

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

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

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

imagen.png

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

之前是:

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

我们改为:

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

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

imagen.png

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

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

offset中间件

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

flip中间件

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

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

imagen.png

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

imagen.png

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

Necesitamos detectar si el elemento de información sobre herramientas en la imagen de arriba ha excedido parcialmente la ventana gráfica del navegador al desplazarse. Solo cuando se detecta que excede podemos voltearlo.

Ideas de detección:

  • El lugar para desplazarse primero no solo puede ser la ventana de la ventana, sino que también se puede desplazar el elemento principal del elemento de información sobre herramientas. En este momento, el desplazamiento del elemento principal del elemento de información sobre herramientas también puede ocultar el elemento de información sobre herramientas durante el desplazamiento, como sigue:

imagen.png

Hay dos barras de desplazamiento en la figura, cualquiera de las cuales se desplaza a un cierto rango puede ocultar el componente del botón azul.

Entonces, necesitamos recopilar todos los contenedores de desplazamiento, cuál es la distancia más corta entre todos los bordes superiores de ellos y el elemento del botón, cuál es la distancia más corta entre todos los bordes izquierdos y el elemento del botón, y lo mismo es cierto para la parte inferior y lados derechos.

De esta forma, se calcula el rango mínimo de actividad de un elemento de botón, y puede ocultarse debido al desplazamiento en cualquier momento dentro de este rango.

Esta función se extrae de @popper-js llamada detectOverflow y finalmente devuelve un objeto:

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

De esta manera, solo necesitamos verificar, por ejemplo, si el elemento popper debe estar en la parte superior en este momento, si su parte superior, izquierda y derecha son todos números positivos. Siempre que haya un número negativo, significa que el elemento popper ha ocultado parte del área en este momento.

Mientras encontremos una posición, sus tres direcciones son todas valores positivos, entonces el elemento popper en esta dirección está completamente dentro del área visible.

La pregunta es, ¿qué pasa si todas las direcciones no están dentro del área visible?

Entonces no importa en este momento, cualquiera que sea la dirección original sigue siendo la misma dirección, lo cual está completamente bien visualmente.

Este artículo ha terminado, si cree que este artículo no es malo, bienvenido a mi proyecto de biblioteca de componentes de reacción satr, todavía está en iteración continua, creo que este tutorial se convertirá en el núcleo más difícil de toda la red y se puede usar en el proyecto de biblioteca de componentes de reacción del entorno de producción !

Supongo que te gusta

Origin juejin.im/post/7264921613544390708
Recomendado
Clasificación