Headline Mask Animation
Was passiert eigentlich, wenn Features oder Animationskonzepte ausgearbeitet werden, welche technisch zunächst einfach wirken, jedoch die Entwickler während der Entwicklung ein wenig ins Schwitzen bringen?
Das Animationskonzept
Für die Überschriften im gesamten Projekt haben wir uns für ein einheitliches Animationskonzept entschieden. Zeilenweise soll sich dabei der Text hinter einer Maske von unten kommend nach oben einschieben.
Der erste Versuch mit GSAP SplitText
Da wir bereits im Projekt GSAP einsetzen, um Animationen aller Art zu realisieren, bot sich diese Bibliothek dazu an, auch diese Animation damit zu erstellen. Gesagt, getan. Relativ schnell konnten wir das gewünschte Animationskonzept mithilfe des GSAP Plugins SplitText umsetzen. SplitText sorgte dafür, dass wir ohne viele Zeilen Code die Überschriften zeilenweise in eigene DOM-Elemente aufteilen konnten, um diese im Anschluss zu animieren. Jetzt noch etwas CSS und fertig war die Animation.
Silbentrennung vs. GSAP SplitText
Jetzt, wo wir die Animation technisch umgesetzt haben, fiel uns auf, dass ab und an die Zeilen nach der Animation ungewollt herumgesprungen sind. Doch was war die Ursache für das Problem? Wir nutzen die automatische Silbentrennung von CSS in Kombination mit dem GSAP Plugin SplitText. Falls nun ein Wort am Ende einer Zeile umgebrochen wird und somit die Silbentrennung greift, wurde das Wort über das Plugin nicht wie erwartet in seine Silben aufgeteilt, sondern als gesamtes Wort in der nächsten Zeile platziert. Sobald nun die SplitText Revert-Funktion, nach Abschluss der Animation, aufgerufen wurde, um den ursprünglichen DOM-Zustand wiederherzustellen, sprang das zunächst nicht umgebrochene Wort in seine Silben um. Warum nun nicht einfach die Revert-Funktion weglassen? Natürlich würde dies unser Problem mit den Springen Wörtern helfen, jedoch verlieren wir somit die automatische Silbentrennung. Dies war nicht unser Anspruch.
Der nächste Versuch
Grundsätzlich war uns nun bekannt, wie wir die zeilenweise Animation der Überschrift umgesetzt bekommen. Die Herausforderung lag nun im zweiten Versuch darin, die automatische Silbentrennung mit unterzubringen. Leichter gesagt als getan.
Die Umsetzung
Zunächst mussten wir einen Lösungsansatz finden, wie wir die Silbentrennung berücksichtigen können. Nach ein paar minimalen HTML- und CSS-Prototypen haben wir uns für die zeilenweise Maskierung mithilfe der CSS-Eigenschaft clip-path entschieden. Das Zwischenergebnis war vielversprechend. Nun ging es ans Eingemachte, denn nicht jede Headline hat die gleiche Schriftgröße, geschweige denn dass die Zeilenanzahl überall identisch ist. Im ersten Schritt bereiten wir unser DOM vor.
<h1 class="line-animation" data-gsap="headline">
<span><span>Headline Mask Animation</span></span>
</h1>
Gleichermaßen wie das GSAP SplitText Plugin haben wir für jede Zeile ein eigenes HTML-Element erzeugt. Allerdings beinhaltet das Element nicht nur den Text dieser einen Zeile, sondern den gesamten Text der Überschrift. Um diese DOM-Elemente zu erzeugen haben wir die Zeilenanzahl berechnet und entsprechend der Anzahl das Ursprungs-Element geklont.
// Calculate how many lines the title has
const height = element.offsetHeight;
const lineHeight = parseFloat(window.getComputedStyle(element).getPropertyValue('line-height'));
const lineCount = parseInt(height / parseInt(lineHeight));
// Clone title content, based on line count
const inner = element.querySelector(':scope > span');
for (let i = 0; i < lineCount - 1; i++) {
const clone = inner.cloneNode(true);
inner.after(clone);
}
Im Anschluss haben wir diese Elemente auf der Z-Achse übereinander platziert und mithilfe der clip-path Eigenschaft entsprechend Ihrer DOM-Reihenfolge, sowie der Zeilenanzahl maskiert. Dadurch sind nun alle Zeilen visuell aufgeteilt.
@for $i from 1 through 5 {
&[data-lines='#{$i}'] {
@for $j from 1 through $i {
> span:nth-last-of-type(#{$j}) {
--_rh-y: calc(var(--_rh-x) * 0.1); // Added amount to consider overhang properly
--_rh-a: calc((#{$j} - 1) * var(--_rh-x) + var(--_rh-y));
--_rh-b: calc(#{$j} * var(--_rh-x) + var(--_rh-y));
clip-path: polygon(0 var(--_rh-a), 100% var(--_rh-a), 100% var(--_rh-b), 0 var(--_rh-b));
> span {
display: block;
clip-path: polygon(0 var(--_rh-a), 100% var(--_rh-a), 100% var(--_rh-b), 0 var(--_rh-b));
}
}
}
}
}
Das DOM ist nun entsprechend vorbereitet und konnte animiert werden. Wie auch schon im ersten Versuch haben wir dabei auf GSAP zurückgegriffen.
Nicht ganz makellos
Doch auch in dieser Variante gibt es einen kleinen Makel. Die Umlaut-Punkte der Großbuchstaben ragen aus der Maske raus, bzw. werden angeschnitten. In unserer Lösung können wir dieses Problem nur mit einer Vergrößerung der Zeilenhöhe beheben. Aus UI-Aspekten haben wir uns gegen diese und Zugunsten der Optik entschieden, sodass die Umlaut-Punkte während der Animation gegebenenfalls angeschnitten oder ähnliches dargestellt werden können. Nach Abschluss der Animation werden diese dann korrekt angezeigt.