Der Stand von CSS-Reflektionen 

Avatar of Ana Tudor
Ana Tudor am

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

Kürzlich habe ich diesen Loader auf CodePen gesehen, eine reine CSS 3D rotierende Reihe von Balken mit einer verblassenden Reflexion. Das wird erreicht, indem für jeden Balken ein Element verwendet wird, dann jedes dieser Elemente dupliziert wird, um die Reflexion zu erzeugen, und schließlich eine Farbverlauf-Abdeckung hinzugefügt wird, um den Verblassungseffekt zu erzeugen. Das klingt ein bisschen so, als würde man sich hinter dem rechten Ohr mit den Zehen des linken Fußes kratzen! Ganz zu schweigen davon, dass die Methode mit der Farbverlauf-Abdeckung für den Verblassungseffekt bei Hintergründen, die keine einfarbigen Farben sind, nicht funktioniert. Gibt es keinen besseren Weg, das mit CSS zu machen?

Die Antwort ist: „Ja“ und „Nein“. „Ja“, es gibt Dinge, die wirklich funktionieren könnten, und „Nein“, sie sind noch nicht wirklich da. Bedauerlicherweise kann der Code zwar mit einem Präprozessor etwas kompakter gestaltet werden (allerdings nicht viel mehr als das, was in einer Schleife erzeugt werden kann), aber die Methode, alle Balken für die Reflexion zu duplizieren und eine Farbverlauf-Abdeckung für den Verblassungseffekt zu verwenden, ist immer noch der beste Weg, dies zu tun, wenn wir kein Canvas verwenden wollen und das Ergebnis über die aktuellen Versionen aller Hauptbrowser hinweg funktionieren soll.

Dieser Artikel wird die Optionen untersuchen, die wir heute zur Erzeugung der Reflexion haben, die „fast“-Lösungen veranschaulichen, wie Cross-Browser-Probleme Schmerzen verursachen und schließlich meine Gedanken dazu diskutieren, was getan werden sollte.

Grundlegende Einrichtung für die Demo

Bevor wir zu Reflexionen kommen, sehen wir uns an, wie wir die Balken erstellen, positionieren und schattieren, da dieser Teil für alle Browser gleich ist.

Erstellung der Balken

Zunächst erstellen wir ein Wrapper-Element .loader mit 10 .bar-Elementen darin.

<div class='loader'>
  <div class='bar'></div>
  <!-- repeat to create 9 more bars -->
</div>

Das Gleiche mehrmals zu schreiben ist mühsam, daher ist es in solchen Situationen einfacher, einen Präprozessor zu verwenden. Wir verwenden hier Haml, aber jeder andere funktioniert ebenfalls.

.loader
  - 10.times do
  .bar

Wir positionieren all diese Elemente absolut, beginnend in der Mitte des Viewports. In den meisten Fällen verwenden wir top: 50%, aber in diesem Fall ist es später bequemer, wenn wir bottom: 50% verwenden.

div {
  position: absolute;
  bottom: 50%; left: 50%;
}

Wir entscheiden uns für eine width und eine height für die Balken und geben ihnen einen background, nur damit wir sie sehen können.

$bar-w: 1.25em;
$bar-h: 5 * $bar-w;

.bar {
  width: $bar-w; height: $bar-h;
  background: currentColor;
}

Wir möchten, dass die untere Kante der Balken mit der Mittellinie des Viewports zusammenfällt (die den Viewport in zwei gleiche Teile teilt), und das haben wir bereits, da wir bottom: 50% verwendet haben.

Zu diesem Zeitpunkt sind unsere Balken alle übereinander gestapelt, ihre linke Kante liegt auf der vertikalen Linie, die den Viewport in zwei gleiche Hälften (links und rechts) teilt, und ihre untere Kante liegt auf der horizontalen Linie, die den Viewport in zwei gleiche Hälften (oben und unten) teilt.

Positionierung der Balken

Wir müssen sie so positionieren, dass die linke Kante des ersten (linkesten) und die rechte Kante des letzten (rechtesten) Balkens im gleichen Abstand von der vertikalen Linie liegen, die den Viewport in zwei gleiche Hälften teilt. Dieser Abstand ist immer die Hälfte der Anzahl der Balken ($n) mal der Breite des Balkens ($bar-w). Die ursprüngliche Demo verwendet Vanilla CSS, aber wir gehen jetzt zu Sass über, um die Code-Menge zu reduzieren.

Das bedeutet, dass wir, ausgehend von der Position, in der sich alle Balken jetzt befinden, den ersten Balken um .5 * $n * $bar-w nach links verschieben müssen. Links ist die negative Richtung der x-Achse, was bedeutet, dass wir ein - (Minus) davor setzen müssen. Der Wert für margin-left für den ersten Balken ist also -.5 * $n * $bar-w.

Der zweite Balken (mit dem 0-basierten Index 1) ist 1 Balkenbreite ($bar-w) nach rechts verschoben (in die positive Richtung der x-Achse). Der Wert für margin-left für diesen Balken ist also -.5 * $n * $bar-w + $bar-w.

Der dritte Balken (mit dem 0-basierten Index 2) ist 2 Balkenbreiten nach rechts verschoben (in die positive Richtung der x-Achse). Der Wert für margin-left für diesen Balken ist also -.5 * $n * $bar-w + 2 * $bar-w.

Der letzte Balken (mit dem 0-basierten Index $n - 1) ist $n - 1 Balkenbreiten nach rechts verschoben (in die positive Richtung der x-Achse). Der Wert für margin-left für diesen Balken ist also -.5 * $n * $bar-w + ($n - 1) * $bar-w.

Im Allgemeinen, wenn wir $i als den 0-basierten Index des aktuellen Balkens betrachten, dann ist der Wert für margin-left für diesen $i$-ten Balken -.5 * $n * $bar-w + $i * $bar-w, was als ($i - .5 * $n) * $bar-w kompakter dargestellt werden kann.

Dies ermöglicht es uns, die Balken mit einer Sass-Schleife zu positionieren.

$n: 10;

@for $i from 0 to $n {
  .bar:nth-child(#{$i + 1}) {
  margin-left: ($i - .5 * $n) * $bar-w;
  }
}

Wir geben ihnen auch einen box-shadow, damit wir sehen können, wo ein Balken endet und der nächste beginnt.

Schattierung der Balken

Die Hintergründe der Balken gehen von einem dunklen Blau (#1e3f57) für den linkesten Balken zu einem hellen Blau (#63a6c1) für den rechtesten über. Das klingt nach einer Aufgabe für die Sass-Funktion mix()! Das erste Argument wäre das helle Blau, das zweite das dunkle Blau und das dritte (relative Gewicht genannt) die Menge (in %) des hellen Blaus, das im resultierenden Mix enthalten sein soll.

Für den ersten Balken wäre diese Menge 0%0% des hellen Blaus im Ergebnis, also wäre dieses Ergebnis nur das dunkle Blau.

Für den letzten Balken wäre die Menge 100%100% des hellen Blaus im Endergebnis (was auch 0% des dunkleren Tons bedeutet), was den Hintergrund hellblau machen würde.

Für die restlichen Balken benötigen wir Zwischenwerte, die gleichmäßig verteilt sind. Wenn wir $n Balken haben, ist der erste Balken bei 0% und der letzte bei 100%, dann müssen wir das Intervall dazwischen in $n - 1 gleiche Intervalle aufteilen.

Im Allgemeinen ist das relative Gewicht für den Balken mit dem Index $i gleich $i * 100% / ($n - 1), was bedeutet, dass wir den folgenden Code hinzufügen müssen:

$c: #63a6c1 #1e3f57; // 1st = light 2nd = dark

@for $i from 0 to $n {
  // list of mix() arguments for current bar
  $args: append($c, $i * 100% / ($n - 1));

  .bar:nth-child(#{$i + 1}) {
  background: mix($args...);
  }
}

Jetzt sehen die Balken aus wie in der Original-Demo.

Erkundung der Optionen für die Reflexion

WebKit-Browser: -webkit-box-reflect

Oh, nein, eine nicht standardisierte Eigenschaft! Ich weiß nicht, warum sie kein Standard wurde. Ich hatte noch nicht einmal von CSS gehört, als diese Eigenschaft zuerst in Safari auftauchte. Aber für WebKit-Browser erledigt sie die Aufgabe und das gut! Viel Arbeit ist darin geflossen. Sie ist einfach zu verwenden und beeinträchtigt nichts in Browsern, die sie nicht unterstützen; sie zeigt einfach keine Reflexion an.

Schauen wir uns an, wie das funktioniert. Der Wert, den sie annimmt, hat drei Teile:

  • eine Richtung, die eines der Schlüsselwörter below, left, above, right sein kann.
  • ein optionaler Offset, der angibt, wie weit von der Kante des Elements die Reflexion beginnen soll (dies ist ein CSS-Längenwert).
  • eine optionale Maske als Bild (die ein CSS-Farbverlauf sein kann).

Das folgende interaktive Demo veranschaulicht dies (klicken Sie auf Richtung, Offset, Farbverlaufswinkel, Stopp-Alphas und Offsets, um sie zu ändern).

Beachten Sie, dass linear-gradient() mehr Stopps haben könnte oder durch radial-gradient() ersetzt werden könnte.

In unserem Fall kommt uns als Erstes in den Sinn, dies auf das .loader-Element anzuwenden.

.loader {
  -webkit-box-reflect: below 0 linear-gradient(rgba(#fff), rgba(#fff, .7));
}

Wenn wir dies jedoch in einem WebKit-Browser testen, sehen wir keine Reflexion!

Was passiert hier? Wir haben all unsere Elemente absolut positioniert und keine expliziten Abmessungen für unser .loader-Element festgelegt, das die Balken enthält. Dies macht es zu einem 0x0-Element – width Null, height Null.

Lassen Sie uns ihm also explizite Abmessungen geben, eine height gleich der der Balken ($bar-h) und eine width, die groß genug ist, um alle Balken zu enthalten ($n * $bar-w). Wir geben ihm auch vorübergehend einen box-shadow, nur damit wir seine Grenzen klar erkennen können.

$loader-w: $n * $bar-w;

.loader {
  width: $loader-w; height: $bar-h;
  box-shadow: 0 0 0 1px red;
}

Ich bevorzuge box-shadow gegenüber outline, wenn es darum geht, die Grenzen eines Elements hervorzuheben, da outline browserübergreifend inkonsistent ist, wenn Kinder ihre Eltern überschreiten.

Wie sich box-shadow und outline in WebKit-Browsern und Edge (oben) im Vergleich zu Firefox (unten) verhalten – outline gibt unterschiedliche Ergebnisse, wenn Kinder überlaufen.

Das Ergebnis der Hinzufügung des obigen Codes ist in WebKit-Browsern im folgenden Pen live zu sehen.

Wenn Sie keinen WebKit-Browser verwenden, sieht es so aus:

Screenshot des Ergebnisses in Chrome, nachdem das .loader-Element explizit dimensioniert wurde.

Wir sehen die Grenzen des Loaders und wir sehen jetzt eine Reflexion, aber die Dinge sind nicht mehr richtig positioniert. Wir wollen, dass der Loader horizontal in der Mitte liegt, also verschieben wir ihn um die Hälfte seiner width nach links. Wir wollen auch, dass die Unterseite der Balken mit der Unterseite ihres Elternteils zusammenfällt, also setzen wir bottom: 0 für sie.

.loader { margin-left: -.5 * $loader-w; }

.bar { bottom: 0; }

Dies behebt das Positionierungsproblem. So sieht es jetzt aus.

Firefox: element() + mask

Erstellung der Reflexion mit element()

Die Funktion element() (noch in Arbeit, bisher nur von Firefox mit dem Präfix -moz- implementiert) gibt uns einen Bildwert, den wir überall dort verwenden können, wo ein tatsächliches Bild verwendet werden kann (funktioniert für background, für border-image, scheint aber nicht als Wert für Pseudo-content zu funktionieren – siehe Bug 1285811). Sie nimmt ein Argument, nämlich den id-Selektor des Elements, das wir als background oder border-image anzeigen möchten. Dies ermöglicht es uns, böse Dinge zu tun, wie z. B. Steuerelementbilder als Hintergründe zu verwenden. Es kann aber auch nützlich sein, wenn wir ein Element in Firefox reflektieren möchten.

Eine sehr wichtige Sache, die man über die Funktion element() wissen sollte, ist, dass sie nicht rekursiv ist – wir können keine Fraktale erstellen, indem wir Elemente als ihre eigenen Hintergründe verwenden. Das macht sie sicher für die Verwendung auf einer Loader-Pseudo für die Erzeugung der Reflexion, sodass wir kein zusätzliches Element benötigen.

Okay, schauen wir uns an, wie wir das machen. Zuerst geben wir unserem Loader-Element eine id (nennen wir sie die offensichtliche loader). Weiter mit dem Styling, wir starten mit genau demselben CSS, das wir in der endgültigen Demo für den WebKit-Fall haben. Dann fügen wir dem Loader eine ::after-Pseudo hinzu, die absolut positioniert ist und ihn vollständig abdeckt.

.loader::after {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  box-shadow: 0 0 0 2px currentColor;
  color: crimson;
  content: 'REFLECTION';
}

Wir haben auch ein paar weitere temporäre Stile hinzugefügt, damit wir eine klare Vorstellung von den Grenzen und der Ausrichtung dieser Pseudo haben, da sie im Endzustand auf dem Kopf stehen soll.

Nun müssen wir unsere ::after-Pseudo an ihrer unteren Kante spiegeln. Um dies zu erreichen, verwenden wir eine scaleY()-Transformation mit einem richtig gewählten transform-origin. Die folgende interaktive Demo veranschaulicht, wie die gerichtete Skalierung für verschiedene Skalierungsfaktoren und Transformationsursprünge funktioniert.

Beachten Sie, dass die Werte für die Skalierungsfaktoren und den transform-origin die Grenzen dieses Demos überschreiten können.

In unserem Fall benötigen wir eine scaleY(-1) und einen transform-origin auf der Linie der unteren Kante der ::after-Pseudo.

ein Element nach unten spiegeln mit einer scaleY(-1)-Transformation mit einem geeigneten transform-origin.

Wir fügen dies dem Code hinzu und setzen #loader als Hintergrund seiner ::after-Pseudo mit der Funktion element() (mit Präfix, da dies derzeit die einzige unterstützte Methode ist).

.loader::after {
  transform-origin: 0 100%;
  transform: scaleY(-1);
  background: -moz-element(#loader);
}

Beachten Sie, dass wir .loader für den Selektor aus Gründen der Spezifität und #loader als Argument der Funktion element() verwenden, da dies ein id-Selektor sein muss.

Das Ergebnis der Hinzufügung des obigen Codes ist im folgenden Pen zu sehen (nur Firefox).

Für alle, die dies in anderen Browsern lesen, hier ist ein Screenshot, wie es aussieht.

Ergebnis der Reflexion mit der Funktion element() in Firefox.
Verblassen der Reflexion mit mask

Wir verblassen die Reflexion auf die gleiche Weise wie im WebKit-Fall: mit einer Maske. In diesem Fall war die Maske eine Komponente des -webkit-box-reflect-Wertes. In diesem Fall sprechen wir von der CSS-Eigenschaft mask, die einen SVG-Referenzwert annimmt.

mask: url(#fader);

Unser #fader-Element ist ein SVG-mask-Element, das ein Rechteck enthält.

<svg>
  <mask id='fader' maskContentUnits='objectBoundingBox'>
    <rect width='1' height='1'/>
  </mask>
</svg>

Wir können das etwas mit Haml komprimieren.

%svg
  %mask#fader(maskContentUnits='objectBoundingBox')
    %rect(width='1' height='1')

Wenn wir das obige jedoch tatsächlich zu unserem Code hinzufügen, verschwindet unsere Reflexion – dies kann durch Betrachten des folgenden Demos in Firefox getestet werden.

Das liegt daran, dass SVG-Formen standardmäßig eine solide schwarze fill haben, die völlig opak ist, und gleichzeitig ist unsere Maske standardmäßig eine Luminanzmaske. Was wir also tun müssen, um die Reflexion zu verblassen, ist, dem Rechteck eine fill zu geben, die eine Referenz auf einen SVG-linearGradient ist.

%rect(width='1' height='1' fill='url(#grad)')

Ein SVG-linearGradient wird zwischen zwei Punkten definiert, die durch die Attribute x1, y1, x2 und y2 angegeben werden. x1 und y1 sind die Koordinaten des Startpunkts (0%) der Farbverlaufslinie, während x2 und y2 die Koordinaten des Endpunkts (100%) dieser Linie sind. Wenn diese fehlen, werden sie als 0%, 0%, 100% und 0% angenommen. Diese Werte beschreiben die Linie von der oberen linken Ecke (0% 0%) zur oberen rechten Ecke (100% 0%) des Elements, auf das sie angewendet wird (da der Standardwert für gradientUnits objectBoundingBox ist), was bedeutet, dass der Farbverlauf standardmäßig von links nach rechts verläuft.

Aber in unserem Fall möchten wir, dass der Farbverlauf von oben nach unten verläuft. Daher ändern wir den Wert für x2 von 100% auf 0% und den Wert für y2 von 0% auf 100%. Dies bewirkt, dass der Farbverlaufsvektor von der oberen linken Ecke (0% 0%) zur unteren linken Ecke (0% 100%) des Elements verläuft, auf das er angewendet wird.

%linearGradient#grad(x2='0%' y2='100%')

Innerhalb des linearGradient-Elements haben wir mindestens zwei stop-Elemente. Diese haben drei spezifische Attribute: offset, stop-color und stop-opacity.

  1. offset kann einen %-Wert annehmen, normalerweise zwischen 0% und 100%, genau wie bei CSS-Farbverläufen. Er kann auch einen numerischen Wert annehmen, normalerweise zwischen 0 und 1.
  2. stop-color kann ein Schlüsselwort, hex, rgb(), rgba(), hsl() oder hsla()-Wert annehmen. Theoretisch. In der Praxis unterstützt Safari semitransparente Werte nicht. Wenn wir also Semitransparenz in unseren Farbverläufen wünschen, sollten wir uns auf das dritte Attribut verlassen...
  3. stop-opacity. Dies nimmt einen Wert zwischen 0 (vollständig transparent) und 1 (vollständig opak) an.

Wir müssen bedenken, dass die Loader-Pseudo, auf die wir die Farbverlaufmaske anwenden, durch eine scaleY(-1)-Transformation nach unten gespiegelt wurde. Das bedeutet, dass die Unterseite unserer Farbverlaufmaske optisch oben ist. Unser Farbverlauf muss also von vollständig transparent oben (optisch unten) zu einer Alpha von .7 unten (optisch oben) verlaufen.

Da unser Farbverlauf von oben nach unten verläuft, ist der erste Stopp der vollständig transparente.

%linearGradient#grad(x2='0%' y2='100%')
  %stop(offset='0' stop-color='#fff' stop-opacity='0')
  %stop(offset='1' stop-color='#fff' stop-opacity='.7')

Das Hinzufügen des linearen Farbverlaufs ergibt auch in Firefox das gewünschte Ergebnis.

Dieser Pen zeigt es live.

Das SVG-Farbverlauf-Problem

In unserem Fall ist die Sache ziemlich einfach, da unser Maskierungsfarbverlauf vertikal ist. Aber was ist mit Farbverläufen, die nicht vertikal oder horizontal sind oder nicht von einer Ecke zur anderen verlaufen? Was, wenn wir einen Farbverlauf in einem bestimmten Winkel haben wollen?

Nun, SVG-Farbverläufe haben auch ein Attribut namens gradientTransform, das die durch die Attribute x1, y1, x2 und y2 definierte Farbverlaufslinie drehen kann. Man könnte meinen, das sei ein einfacher Weg, CSS-Farbverläufe in einem Winkel zu reproduzieren. Aber... so einfach ist das nicht!

Betrachten wir den Fall eines Farbverlaufs von Gold zu Karmesinrot. Um die Dinge klarer zu machen, geben wir ihm einen scharfen Übergang zwischen den beiden bei 50%. Zunächst nehmen wir den Winkel der CSS-Version dieses Farbverlaufs als 0deg an. Das bedeutet, dass der Farbverlauf von 0% unten (Gold) zu 100% oben (Karmesinrot) verläuft. Der CSS-Code zur Erstellung dieses Farbverlaufs wäre:

background-image: 
  linear-gradient(0deg, #e18728 50%, #d14730 0);

Wenn es Dinge gibt, die Sie nicht verstehen, wie CSS-lineare Farbverläufe funktionieren, können Sie sich diesen hervorragenden Artikel von Patrick Brosset ansehen.

Das Ergebnis ist im folgenden Pen zu sehen.

Um dies mit SVG zu reproduzieren, erstellen wir einen Farbverlauf, bei dem y1 100%, y2 0% ist und x1 und x2 den gleichen Wert haben (wir nehmen ihn der Einfachheit halber 0). Das bedeutet, dass die Farbverlaufslinie vertikal von unten nach oben verläuft. Wir setzen auch beide Stopp-Offsets auf 50%.

linearGradient#g(y1='100%' x2='0%' y2='0%'
                 gradientTransform='rotate(0 .5 .5)')
  stop(offset='50%' stop-color='#e18728')
  stop(offset='50%' stop-color='#d14730')

Hinweis des Herausgebers: Ich fragte Ana, warum hier zu Jade gewechselt wurde, und sie sagt: Ich habe anfangs Haml verwendet, da ich eine Schleifenvariable vermeiden konnte, die ich sowieso nirgends verwendet habe. Später habe ich Jade verwendet, da es Variablen und Berechnungen zulässt.

Dieser Farbverlauf ist noch nicht gedreht, daher ist der Wert für sein gradientTransform-Attribut zu diesem Zeitpunkt rotate(0 .5 .5). Die beiden letzten Werte geben die Koordinaten des Punktes an, um den der Farbverlauf gedreht wird, relativ zum Element, auf das der Farbverlauf angewendet wird. 0 0 bedeutet die obere linke Ecke, 1 1 die untere rechte Ecke und .5 .5 ist genau in der Mitte. Dies ist im folgenden Pen live zu sehen.

Wenn wir wollen, dass unser Farbverlauf von links nach rechts verläuft, dann ändern wir im Fall des CSS-Farbverlaufs den Winkel von 0deg zu 90deg.

background-image: 
  linear-gradient(90deg, #e18728 50%, #d14730 0);

Um das gleiche Ergebnis mit dem SVG-Farbverlauf zu erzielen, ändern wir den Wert von gradientTransform zu rotate(90 .5 .5).

linearGradient#g(y1='100%' x2='0%' y2='0%'
                 gradientTransform='rotate(90 .5 .5)')
  // same stops as before

Bisher alles gut. Es scheint nicht so mühsam zu sein, einen CSS-Farbverlauf mit SVG zu reproduzieren. Aber versuchen wir auch andere Winkel. Im interaktiven Demo unten haben wir einen CSS-Farbverlauf links und die SVG-Version rechts. Die violette Linie ist die Farbverlaufslinie und sie sollte senkrecht zur scharfen Trennlinie zwischen Gold und Karmesinrot verlaufen. Das Ziehen des Schiebereglers ändert den Farbverlaufswinkel sowohl für den CSS- als auch für den SVG-Fall. Und wir sehen, dass bei Werten, die keine Vielfachen von 90deg sind, etwas nicht stimmt.

Wie das obige Demo zeigt, erhalten wir bei Werten, die keine Vielfachen von 90deg sind, nicht das gleiche Ergebnis. Wir würden das gleiche Ergebnis erhalten, wenn die Elemente, auf die wir die Farbverläufe anwenden, quadratisch wären. Das bedeutet, dass wir den Farbverlauf auf ein größeres quadratisches Element anwenden können, das wir dann auf unser eigentliches Element zuschneiden. Aber all das macht die Methode zur Erzeugung von verblassenden Reflexionen mit element() und mask komplizierter.

Edge: nur noch SVG?

Leider funktioniert keine der beiden bisher vorgestellten Methoden in Edge. Unsere einzige Lösung, die auch in Edge funktionieren würde und keine manuelle Duplizierung jedes einzelnen Balkens erfordert, wäre, alles bisherige fallen zu lassen und den Loader mit SVG neu zu erstellen. Dies hat den Vorteil, dass es sich um eine Cross-Browser-Methode handelt.

Im Wesentlichen erstellen wir ein SVG-Element mit einem viewBox, so dass sein 0 0 Punkt genau in der Mitte liegt. Wir definieren einen Balken, dessen untere Kante auf der x-Achse und dessen linke Kante auf der y-Achse liegt. Dann klonen wir diesen Balken (über das SVG-use-Element) so oft wie nötig innerhalb einer #loader-Gruppe. Die Positionierung dieser Klone handhaben wir genauso wie zuvor.

- var bar_w = 125, bar_h = 5 * bar_w;
- var n = 10;
- var vb_w = n * bar_w;
- var vb_h = 2 * bar_h;
- var vb_x = -.5 * vb_w, vb_y = -.5 * vb_h;

svg(viewBox=[vb_x, vb_y, vb_w, vb_h].join(' '))
  defs
    rect#bar(y=-bar_h width=bar_w height=bar_h)

  g#loader
    - for(var i = 0; i < n; i++) {
      - var x = (i - .5 * n) * bar_w;
      use(xlink:href='#bar' x=x)
    - }

Das Ergebnis des obigen Codes ist im folgenden Pen zu sehen.

Nachdem wir diese Balken erstellt haben, möchten wir das svg-Element schön positionieren und das tun wir mit Flexbox. Wir möchten die Balken auch genauso schattieren wie zuvor. Das alles machen wir aus dem SCSS.

$n: 10;
$c: #63a6c1 #1e3f57;
$bar-w: 1.25em;
$bar-h: 5 * $bar-w;
$loader-w: $n * $bar-w;
$loader-h: 2 * $bar-h;

body {
  display: flex;
  justify-content: center;
  margin: 0;
  height: 100vh;
}

svg {
  align-self: center;
  width: $loader-w; height: $loader-h;
}

@for $i from 0 to $n {
  $args: append($c, $i * 100%/($n - 1));

  [id='loader'] use:nth-child(#{$i + 1}) {
    fill: mix($args...);
  }
}

Dieser Pen zeigt das Ergebnis der Hinzufügung des obigen Codes.

Wir klonen unsere #loader-Gruppe (wiederum mit einem use-Element). Wir spiegeln diesen Klon mit einer scale(1 -1)-Funktion und wenden eine Maske darauf an, genau wie wir es zuvor für das Pseudo-Element getan haben. Standardmäßig werden SVG-Elemente relativ zum 0, 0-Punkt der SVG-Leinwand skaliert, der sich in diesem Fall am unteren Rand unseres Loaders befindet, was perfekt ist, um den Loader-Klon nach unten zu spiegeln; wir müssen keinen transform-origin setzen.

use(xlink:href='#loader' transform='scale(1 -1)')

Wir verwenden das Attribut transform anstelle eines CSS-Transforms, da CSS-Transforms in Edge nicht unterstützt werden – wenn Sie sie wünschen, stimmen Sie dafür!

Wir haben jetzt eine Reflexion, wie im folgenden Pen zu sehen ist.

Der letzte Schritt ist das Verblassen unserer Reflexion mit einer mask. Es ist genau die gleiche Methode und der gleiche Code wie zuvor, daher werden wir ihn nicht noch einmal durchgehen. Der vollständige Code befindet sich im endgültigen Pen für diese Methode, den Sie unten überprüfen können.

Animation

Die CSS-Animation im Original-Pen ist eine ziemlich einfache 3D-Rotation der Balken.

@keyframes bar {
  0% {
  transform: rotate(-.5turn) rotateX(-1turn);
  }
  75%, 100% { transform: none; }
}

Es ist die gleiche Animation für alle Balken.

animation: bar 3s cubic-bezier(.81, .04, .4, .7) infinite;

Wir fügen nur eine andere Verzögerung für jeden innerhalb der Balkenschleife hinzu.

animation-delay: $i*50ms;

Und da wir die Balken in 3D rotieren, fügen wir auch eine perspective zum Loader-Element hinzu.

Aber das funktioniert nur wie beabsichtigt in WebKit-Browsern, die die -webkit-box-reflect-Methode verwenden.

Aufnahme des Endergebnisses mit -webkit-box-reflect in Chrome.

Wir haben auch einen Bildhintergrund hinzugefügt, nur um zu zeigen, wie das aussehen würde. Die fertige Demo in diesem nur für WebKit-Browser ist:

Wir können versuchen, sie auch in Firefox zum Laufen zu bringen. Wenn wir jedoch den Animationscode zu der Version hinzufügen, die auch in Firefox funktioniert, sehen die Dinge nicht ganz richtig aus.

Aufnahme der anfänglichen animierten Version mit element() und mask in Firefox.

Wir haben hier ein paar Probleme, wie es live in Firefox getestet werden kann.

Das erste Problem ist, dass die Reflexion über die Grenzen der Pseudo hinaus abgeschnitten wird. Wir können das beheben, indem wir die Abmessungen des Loader-Elements (und damit auch die der Pseudo) erhöhen.

$loader-w: ($n + 1) * $bar-w + $bar-h;

Aber es gibt nichts, was wir gegen die anderen beiden Probleme tun können – die Reflexion wird nicht reibungslos aktualisiert, während die Balken in 3D rotieren, und die Anwesenheit der perspective-Eigenschaft lässt die Balken verschwinden (siehe Bug 1282312).

Aufnahme der animierten Version mit element() und mask in Firefox (mit Perspektive).
Aufnahme der animierten Version mit element() und mask in Firefox (ohne Perspektive).

Live-Test für Firefox (Sie können die perspective ein- und ausschalten, um den Unterschied zu sehen).

Was ist mit der reinen SVG-Lösung? Nun, leider funktionieren die im obigen Keyframe-Set verwendeten CSS 3D-Transforms nicht. CSS-Transforms werden in Edge für SVG-Elemente noch nicht unterstützt, weshalb wir uns zuvor auf das Attribut transform zur Erstellung der Reflexion verlassen haben. Aber die Werte des transform-Attributs sind streng 2D und wir können sie sowieso nicht ohne JavaScript animieren (manche denken vielleicht an SMIL, aber das ist Markup-Gekritzel, es wurde nie in Edge/IE unterstützt und wurde jetzt in Chrome als veraltet eingestuft).

Es gibt also derzeit absolut keine Möglichkeit, diese Art von Balken-Loader-Demo so zu erstellen, dass sie sowohl browserübergreifend funktioniert als auch jedes einzelne Balken-Element nicht dupliziert. Alles, was wir tun können, ist, zwei Loader-Elemente zu haben, jedes mit der gleichen Anzahl von Balken.

- 2.times do
  .loader
    - 10.times do 
      .bar

Die Balken sind genauso gestylt wie zuvor und wir spiegeln das zweite Loader-Element mit einer scale(-1)-Transformation nach unten.

.loader:nth-child(2) {
  transform: scaleY(-1);
}

Wir fügen die Balken-Animation hinzu und erhalten folgendes Ergebnis.

Jetzt müssen wir die Reflexion verblassen lassen. Leider können wir keine mask auf das zweite Loader-Element anwenden, da Maskierung nur bei SVG-Elementen browserübergreifend funktioniert. Edge unterstützt die Maskierung von HTML-Elementen noch nicht, aber Sie können dafür stimmen.

Das Einzige, was wir tun können, ist, eine Farbverlauf-Abdeckung für den zweiten Loader (den gespiegelten) zu verwenden. Beachten Sie, dass dies auch bedeutet, dass wir keinen Bildhintergrund haben können. Ein fester Hintergrund oder, in sehr begrenzten Fällen, ein Farbverlaufhintergrund muss ausreichen. Wir erstellen diese Abdeckung aus dem ::after-Pseudo-Element des zweiten Loaders und machen sie groß genug, um die Balken auch während der Rotation abzudecken.

$bgc: #eee;
$cover-h: $bar-w + $bar-h;
$cover-w: $n * $bar-w + $cover-h;

html { background: $bgc; }

.loader:nth-child(2)::after {
  margin-left: -.5 * $cover-w;
  width: $cover-w; height: $cover-h;
  background: linear-gradient($bgc $bar-w, rgba($bgc, .3));
  content: '';
}

Das Ergebnis ist in folgendem Pen zu sehen

Abschließende Gedanken

Wir brauchen eine bessere browserübergreifende Lösung dafür. Ich glaube, dass das Spiegeln eines Elements nicht das Duplizieren all seiner Nachkommen beinhalten sollte, wie wir es für diesen Balken-Loader tun mussten. Dass wir nicht zu einer SVG-Lösung wechseln müssen (die auch ihre eigenen Probleme mit sich bringt), nur um die Reflexion verblassen zu lassen und einen Bild-background dahinter zu haben.

Welche ist die bessere Lösung? -webkit-box-reflect oder element() + mask? Ich weiß es nicht. Persönlich hätte ich gerne beides browserübergreifend verfügbar.

Ich war früher sicher, dass ich kein zusätzliches Element für die Reflexion wollte, obwohl ein :reflection-Pseudo-Element vernünftig klang. Aber jetzt gibt es etwas, das ich lieber hätte, als kein zusätzliches Element zu haben. Es ist die Freiheit, mehrere Reflexionen in verschiedenen Richtungen zu erstellen und diese Reflexionen auf verschiedene Weise zu transformieren, wie z. B. in 3D zu drehen oder zu verzerren. Die Verwendung der element()-Methode ermöglicht all diese Dinge, weshalb ich sie mag. Ganz zu schweigen davon, dass die Verwendung von SVG für die Maskierung bedeutet, dass wir komplexere Masken auf diese Reflexionen anwenden und coolere Effekte erzielen können.

Auf der anderen Seite bringt große Macht große Verantwortung mit sich. Vielleicht können Sie es sich nicht leisten, die Zeit zu nehmen, sich mit all den Feinheiten hinter der mächtigeren Methode vertraut zu machen. Manchmal wollen Sie einfach nur eine einfache Methode, um ein einfaches Ergebnis zu erzielen.