Headline Mask Animation
What actually happens when features or animation concepts are developed that seem technically simple at first, but make the developers sweat a little during development?
The animation concept
We opted for a uniform animation concept for the headlines throughout the project. Line by line, the text behind a mask should slide upwards from the bottom.
The first attempt with GSAP SplitText
Since we already use GSAP in the project to realize all kinds of animations, this library was the perfect choice for creating this animation as well. No sooner said than done. We were able to implement the desired animation concept relatively quickly with the help of the GSAP plugin SplitText. SplitText ensured that we were able to split the headings line by line into separate DOM elements without many lines of code in order to animate them afterwards. Now a little CSS and the animation was ready.
Hyphenation vs. GSAP SplitText
Now that we have technically implemented the animation, we noticed that the lines after the animation were jumping around unintentionally from time to time. But what was the cause of the problem? We use the automatic hyphenation of CSS in combination with the GSAP plugin SplitText. If a word is now wrapped at the end of a line and the hyphenation thus takes effect, the word was not split into its syllables via the plugin as expected, but placed as a whole word in the next line. As soon as the SplitText Revert function was called after the animation was completed in order to restore the original DOM state, the word that was initially not split into its syllables jumped back into its syllables. Why not simply omit the revert function? Of course, this would help our problem with the jumping words, but we would lose the automatic hyphenation. This was not our intention.
The next attempt
Basically, we now knew how to implement the line-by-line animation of the heading. The challenge in the second attempt was to incorporate automatic hyphenation. Easier said than done.
The implementation
First of all, we had to find a way to take hyphenation into account. After a few minimal HTML and CSS prototypes, we opted for line-by-line masking using the CSS property clip-path. The interim result was promising. Now it was time to get down to business, because not every headline has the same font size, not to mention that the number of lines is identical everywhere. The first step was to prepare our DOM.
<h1 class="line-animation" data-gsap="headline">
<span><span>Headline Mask Animation</span></span>
</h1>
Just like the GSAP SplitText plugin, we have created a separate HTML element for each line. However, the element contains not only the text of this one line, but the entire text of the heading. To create these DOM elements, we have calculated the number of lines and cloned the original element according to this number.
// 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);
}
We then placed these elements on top of each other on the Z-axis and masked them according to their DOM order and the number of rows using the clip-path property. As a result, all rows are now visually divided.
@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));
}
}
}
}
}
DOM is now prepared accordingly and could be animated. As in the first attempt, we used GSAP for this.
Not quite flawless
However, there is also a small flaw in this version. The umlaut dots of the capital letters protrude from the mask or are cut off. In our solution, we can only solve this problem by increasing the line height. For UI reasons, we have decided against this and in favor of the visual appearance, so that the umlaut dots can be displayed truncated or similar during the animation if necessary. Once the animation is complete, they will be displayed correctly.