Smartere Wege zur Generierung einer tief verschachtelten HTML Struktur

Avatar of Ana Tudor
Ana Tudor am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Nehmen wir an, wir möchten die folgende HTML-Struktur haben

<div class='boo'>
  <div class='boo'>
    <div class='boo'>
      <div class='boo'>
        <div class='boo'></div>
      </div>
    </div>
  </div>
</div>

Das ist wirklich mühsam, manuell zu schreiben. Und der Grund, warum dieser Beitrag entstanden ist, war die Entrüstung, als ich sah, wie er mit Haml so generiert wurde

.boo
  .boo
    .boo
      .boo
        .boo

Im Code, den ich sah, gab es tatsächlich etwa zwanzig Verschachtelungsebenen, aber vielleicht lesen einige Leute auf einem Mobiltelefon, also füllen wir nicht den gesamten Viewport mit „Boo“, auch wenn Halloween naht.

Wie Sie wahrscheinlich erkennen können, ist das manuelle Ausschreiben jeder Ebene alles andere als ideal, insbesondere wenn die HTML von einem Präprozessor (oder von JavaScript oder sogar einer serverseitigen Sprache wie PHP) generiert wird. Ich persönlich bin kein Fan von tiefer Verschachtelung und verwende sie nicht oft selbst, aber wenn Sie es dennoch tun, denke ich, dass es sich lohnt, dies auf eine Weise zu tun, die gut skalierbar und leicht zu warten ist.

Betrachten wir also zuerst einige bessere Lösungen für diesen Basisfall und seine Variationen, und sehen Sie sich dann einige unterhaltsame Dinge an, die mit dieser Art von tiefer Verschachtelung gemacht werden!

Die Basislösung

Was wir hier brauchen, ist ein rekursiver Ansatz. Zum Beispiel macht mit Haml das folgende Code-Snippet den Trick

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

= nest('👻', 5)

Darin gibt es eine Emoji-Klasse, weil wir das können und weil dies nur ein kleines unterhaltsames Beispiel ist. Ich würde definitiv keine Emoji-Klassen auf einer tatsächlichen Website verwenden, aber in anderen Situationen spiele ich gerne ein bisschen mit dem Code, den ich schreibe.

Wir können das HTML auch mit Pug generieren

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

+nest('👻', 5)

Dann gibt es noch die JavaScript-Option

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)

Mit PHP können wir etwas Ähnliches verwenden

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

nest('👻', 5);
?>

Beachten Sie, dass der Hauptunterschied zwischen dem, was jede dieser erzeugt, mit Formatierung und Leerzeichen zusammenhängt. Das bedeutet, dass das Anvisieren des innersten „Boo“ mit .👻:empty für das mit Haml, JavaScript und PHP generierte HTML funktioniert, aber für das mit Pug generierte fehlschlägt.

Hinzufügen von Ebenenindikatoren

Nehmen wir an, wir möchten, dass jedes unserer „Boo“ einen Ebenenindikator als benutzerdefinierte Eigenschaft --i hat, die dann beispielsweise verwendet werden könnte, um jedem von ihnen einen anderen background zu geben.

Sie denken vielleicht, wenn wir nur die Farbton ändern wollen, dass wir das mit filter: hue-rotate() tun können und ohne Ebenenindikatoren auskommen. hue-rotate() beeinflusst jedoch nicht nur den Farbton, sondern auch die Sättigung und Helligkeit. Es bietet auch nicht das gleiche Maß an Kontrolle wie die Verwendung unserer eigenen benutzerdefinierten Funktionen, die von einem Ebenenindikator --i abhängen.

Dies ist zum Beispiel etwas, das ich in einem kürzlich durchgeführten Projekt verwendet habe, um background-Komponenten reibungslos von Ebene zu Ebene ändern zu lassen (die $c-Werte sind Polynomkoeffizienten)

--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));

Das Tuning von Pug zum Hinzufügen von Ebenenindikatoren sieht wie folgt aus

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

+nest('👻', 5)

Die Haml-Version ist auch nicht viel anders

- 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)

Mit JavaScript haben wir

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)

Und mit PHP sieht der Code so aus

<?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);
?>

Eine baumähnlichere Struktur

Nehmen wir an, wir möchten, dass jedes unserer „Boo“ zwei „Boo“-Kinder hat, für eine Struktur, die so aussieht

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

Glücklicherweise müssen wir unseren Pug-Mixin nicht wesentlich ändern, um das zu erreichen (Demo)

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

+nest('👻', 5)

Das Gleiche gilt für die Haml-Version

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

= nest('👻', 5)

Die JavaScript-Version erfordert etwas mehr Aufwand, aber nicht zu viel

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)

Mit PHP müssen wir die Funktion nest() nur einmal mehr im if-Block aufrufen

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

nest('👻', 5);
?>

Das Top-Level-Element anders stylen

Wir könnten natürlich eine spezielle Klasse .top (oder .root oder etwas Ähnliches) nur für die oberste Ebene hinzufügen, aber ich ziehe es vor, dies dem CSS zu überlassen

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

Vorsicht!

Einige Eigenschaften wie transform, filter, clip-path, mask oder opacity wirken sich nicht nur auf ein Element aus, sondern auch auf alle seine Nachkommen. Manchmal ist dies der gewünschte Effekt und genau der Grund, warum die Verschachtelung dieser Elemente gegenüber Geschwistern bevorzugt wird.

Manchmal ist dies jedoch nicht das, was wir wollen, und obwohl es möglich ist, die Auswirkungen von transform und manchmal sogar filter umzukehren, können wir bei den anderen nichts tun. Wir können zum Beispiel nicht opacity: 1.25 auf ein Element setzen, um seine Eltern mit opacity: .8 zu kompensieren.

Beispiele!

Zuerst haben wir diesen reinen CSS-Punktlader, den ich kürzlich für eine CodePen-Herausforderung erstellt habe

Hier addieren sich die Effekte der Skalierungstransformationen und der animierten Rotationen auf den inneren Elementen, ebenso wie die Deckkraft.

Als Nächstes kommt dieser Yin-und-Yang-Tanz, der die baumähnliche Struktur verwendet

Für jedes Element, außer dem äußersten (:not(.☯️) > .☯️), ist der Durchmesser gleich der Hälfte des Durchmessers seines Elternelements. Für die innersten Elemente (.☯️:empty, die wir wohl als Baumblätter bezeichnen können), hat der background zwei zusätzliche radial-gradient()-Schichten. Und genau wie beim ersten Demo addieren sich die Effekte der animierten Rotationen auf den inneren Elementen.

Ein weiteres Beispiel wären diese sich drehenden Bonbon-Tentakel

Jeder der konzentrischen Ringe stellt eine Verschachtelungsebene dar und kombiniert die Effekte der animierten Rotationen all seiner Vorfahren mit seinen eigenen.

Schließlich haben wir diese Demo mit dreieckigen Öffnungen (beachten Sie, dass sie einzelne Transformationseigenschaften wie rotate und scale verwendet, daher muss das Flag **Experimental Web Platform features** in chrome://flags aktiviert sein, um es in Chromium-Browsern zu sehen

Dreieckige Öffnungen (Live-Demo).

Dies verwendet eine leicht modifizierte Version des grundlegenden Verschachtelungs-Mixins, um jedem Level auch eine color zuzuweisen

- 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)

Animiert werden hier die einzelnen Transformationseigenschaften scale und rotate. Dies geschieht, damit wir ihnen unterschiedliche Timing-Funktionen zuweisen können.