formas más inteligentes de generar una estructura HTML profundamente anidada | Programar Plus

Digamos que queremos tener la siguiente estructura HTML:

<div class="boo">
  <div class="boo">
    <div class="boo">
      <div class="boo">
        <div class="boo"></div>
      </div>
    </div>
  </div>
</div>

Eso es realmente un dolor para escribir manualmente. Y la razón por la que nació este post fue el horror de verlo generado con Haml así:

.boo
  .boo
    .boo
      .boo
        .boo

En realidad, había unos veinte niveles de anidamiento en el código que vi, pero tal vez algunas personas estén leyendo cosas en un teléfono móvil, así que no llenemos toda la ventana de visualización con abucheos, incluso si Halloween está cerca.

Como probablemente se dará cuenta, escribir manualmente cada nivel está lejos de ser lo ideal, especialmente cuando el HTML es generado por un preprocesador (o desde JavaScript, o incluso un lenguaje de back-end como PHP). Personalmente, no soy un fanático del anidamiento profundo y no lo uso mucho, pero si lo desea de todos modos, creo que vale la pena hacerlo de una manera que se escale bien y sea fácil de mantener.

¡Así que primero echemos un vistazo a algunas mejores soluciones para este caso base y variaciones en él y luego veamos algunas cosas divertidas hechas con este tipo de anidamiento profundo!

La solución básica

Lo que necesitamos aquí es un enfoque recursivo. Por ejemplo, con Haml, el siguiente fragmento de código funciona:

- def nest(cls, n);
-  return '' unless n > 0;
-  "<div class="#{cls}">#{nest(cls, n - 1)}</div>"; end

= nest('👻', 5)

Hay una clase de emoji porque podemos y porque este es solo un pequeño ejemplo divertido. Definitivamente no usaría clases de emoji en un sitio web real, pero en otras situaciones, me gusta divertirme un poco con el código que escribo.

También podemos generar el HTML con Pug:

mixin nest(cls, n)
  div(class=cls)
    if --n
      +nest(cls, n)

+nest('👻', 5)

Luego también está la opción de JavaScript:

function nest(_parent, cls, n) {
  let _el = document.createElement('div');
	
  if(--n) nest(_el, cls, n);

  _el.classList.add(cls);
  _parent.appendChild(_el)
};

nest(document.body, '👻', 5)

Con PHP, podemos usar algo como esto:

<?php
function nest($cls, $n) {
  echo "<div class="$cls">";
  if(--$n > 0) nest($cls, $n);
  echo "</div>";
}

nest('👻', 5);
?>

Tenga en cuenta que la principal diferencia entre lo que cada uno de estos produce está relacionada con el formato y el espacio en blanco. Esto significa que apuntar al “abucheo” más interno con .👻:empty funcionará para el HTML generado por Haml, JavaScript y PHP, pero fallará para el generado por Pug.

Adición de indicadores de nivel

Digamos que queremos que cada uno de nuestros abucheos tenga un indicador de nivel como propiedad personalizada --i, que luego podría usarse para darle a cada uno de ellos un background, por ejemplo.

Puede estar pensando que, si todo lo que queremos es cambiar el tono, entonces podemos hacerlo con filter: hue-rotate() y prescindir de indicadores de nivel. Sin embargo, hue-rotate() no solo afecta el tono, sino también la saturación y la luminosidad. Tampoco proporciona el mismo nivel de control que usar nuestras propias funciones personalizadas que dependen de un indicador de nivel, --i.

Por ejemplo, esto es algo que usé en un proyecto reciente para hacer background componentes cambian suavemente de un nivel a otro (el $c los valores son coeficientes polinómicos):

--sq: calc(var(--i)*var(--i)); /* square */
--cb: calc(var(--sq)*var(--i)); /* cube */
--hue: calc(#{$ch0} + #{$ch1}*var(--i) + #{$ch2}*var(--sq) + #{$ch3}*var(--cb));
--sat: calc((#{$cs0} + #{$cs1}*var(--i) + #{$cs2}*var(--sq) + #{$cs3}*var(--cb))*1%);
--lum: calc((#{$cl0} + #{$cl1}*var(--i) + #{$cl2}*var(--sq) + #{$cl3}*var(--cb))*1%);

background: hsl(var(--hue), var(--sat), var(--lum));

Ajustar el Pug para agregar indicadores de nivel se ve de la siguiente manera:

mixin nest(cls, n, i = 0)
  div(class=cls style=`--i: ${i}`)
    if ++i < n
      +nest(cls, n, i)

+nest('👻', 5)

La versión de Haml tampoco es muy diferente:

- def nest(cls, n, i = 0);
-   return '' unless i < n;
-   "<div class="#{cls}" style="--i: #{i}">#{nest(cls, n, i + 1)}</div>"; end

= nest('👻', 5)

Con JavaScript, tenemos:

function nest(_parent, cls, n, i = 0) {
  let _el = document.createElement('div');

  _el.style.setProperty('--i', i);
	
  if(++i < n) nest(_el, cls, n, i);

  _el.classList.add(cls);
  _parent.appendChild(_el)
};

nest(document.body, '👻', 5)

Y con PHP, el código se ve así:

<?php
function nest($cls, $n, $i = 0) {
  echo "<div class="$cls" style="--i: $i">";
  if(++$i < $n) nest($cls, $n, $i);
  echo "</div>";
}

nest('👻', 5);
?>

Una estructura más parecida a un árbol.

Digamos que queremos que cada uno de nuestros boos tenga dos hijos boo, para una estructura que se ve así:

.boo
  .boo
    .boo
      .boo
      .boo
    .boo
      .boo
      .boo
  .boo
    .boo
      .boo
      .boo
    .boo
      .boo
      .boo

Afortunadamente, no tenemos que cambiar mucho nuestra mezcla base de Pug para obtener esto (demostración):

mixin nest(cls, n)
  div(class=cls)
    if --n
      +nest(cls, n)
      +nest(cls, n)

+nest('👻', 5)

Lo mismo ocurre con la versión de Haml:

- def nest(cls, n);
-   return '' unless n > 0;
-   "<div class="#{cls}">#{nest(cls, n - 1)}#{nest(cls, n - 1)}</div>"; end

= nest('👻', 5)

La versión de JavaScript requiere un poco más de esfuerzo, pero no demasiado:

function nest(_parent, cls, n) {
  let _el = document.createElement('div');
  
  if(n > 1) {
    nest(_el, cls, n - 1);
    nest(_el, cls, n - 1)
  }

  _el.classList.add(cls);
  _parent.appendChild(_el)
};

nest(document.body, '👻', 5)

Con PHP, solo necesitamos llamar al nest() funcionar una vez más en el if cuadra:

<?php
function nest($cls, $n) {
  echo "<div class="$cls">";
  if(--$n > 0) {
    nest($cls, $n);
    nest($cls, $n);
  }
  echo "</div>";
}

nest('👻', 5);
?>

Diseñar el elemento de nivel superior de manera diferente

Por supuesto, podríamos añadir un especial .top (o .root o algo similar) class solo para el nivel superior, pero prefiero dejar esto al CSS:

:not(.👻) > .👻 {
  /* Top-level styles*/
}

¡Cuidado!

Algunas propiedades, como transform, filter, clip-path, mask o opacity no solo afectan a un elemento, sino también a todos sus descendientes. En ocasiones este es el efecto buscado y precisamente el motivo por el que se prefiere anidar estos elementos a que sean hermanos.

Sin embargo, otras veces puede que no sea lo que queremos, y si bien es posible revertir los efectos de transform y a veces incluso filter, no hay nada que podamos hacer con los demás. No podemos, por ejemplo, establecer opacity: 1.25 en un elemento para compensar que su padre tenga opacity: .8.

¡Ejemplos!

En primer lugar, tenemos este cargador de puntos de CSS puro que hice recientemente para un desafío de CodePen:

Aquí, los efectos de las transformaciones de escala y de las rotaciones animadas se suman a los elementos internos, al igual que las opacidades.

El siguiente es este baile de yin y yang, que utiliza la estructura en forma de árbol:

Para cada elemento, excepto el más externo (:not(.☯️) > .☯️), el diámetro es igual a la mitad del de su padre. Para los elementos más internos (.☯️:empty, que supongo que podemos llamar las hojas del árbol), el background tiene dos adicionales radial-gradient() capas. Y al igual que la primera demostración, los efectos de las rotaciones animadas se suman a los elementos internos.

Otro ejemplo serían estos tentáculos de caramelo giratorios:

Cada uno de los anillos concéntricos representa un nivel de anidamiento y combina los efectos de las rotaciones animadas de todos sus ancestros con los suyos propios.

Finalmente, tenemos esta demostración de aperturas triangulares (tenga en cuenta que está usando propiedades de transformación individuales como rotate y scale entonces el Características de la plataforma web experimental la bandera debe estar habilitada en chrome://flags para verlo funcionando en los navegadores Chromium):

Aberturas triangulares (demostración en vivo).

Esto utiliza una versión ligeramente modificada de la mezcla básica de anidamiento para establecer también un color en cada nivel:

- let c = ['#b05574', '#f87e7b', '#fab87f', '#dcd1b4', '#5e9fa3'];
- let n = c.length;

mixin nest(cls, n)
  div(class=cls style=`color: ${c[--n]}`)
    if n
      +nest(cls, n)

body(style=`background: ${c[0]}`)
  +nest('🔺', n)

Lo que se anima aquí son las propiedades de transformación individuales. scale y rotate. Esto se hace para que podamos establecer diferentes funciones de temporización para ellos.

(Visited 4 times, 1 visits today)