On this tutorial, we’ll learn to cut up a component’s textual content into separate characters, which we’ll then animate to present us a twisting impact.
What We’ll be Constructing
With out additional intro, let’s take a look at what we’ll be constructing:
In our case, there can be two kinds of animations:
- The primary animation will occur every time a heading comes into view.
- The second animation will occur every time the consumer hovers over a hyperlink.
1. Start With the HTML Markup
We’ll outline three sections. Every part may have a heading, a paragraph, and a hyperlink.
We’ll add the data-split
attribute to the weather that can be animated. An extra data-split-type
attribute will decide the animation sort. Attainable values are hover
and scroll
.
Right here’s the required markup:
<part> <div> <h2 data-split data-split-type="scroll">...</h2> <p>...</p> <a href="" data-split data-split-type="hover">...</a> </div> </part> <part> <div> <h2 data-split data-split-type="scroll">...</h2> <p>...</p> <a href="" data-split data-split-type="hover">...</a> </div> </part> <part> <div> <h2 data-split data-split-type="scroll">...</h2> <p>...</p> <a href="" data-split data-split-type="hover">...</a> </div> </part>
2. Cut up Textual content
The splitCharacters()
operate can be accountable for splitting the textual content of all parts with the data-split
attribute and wrapping every of their characters right into a span
component.
So, assuming that originally, we have now this component:
<a href="" data-split data-split-type="hover">Hover me →</a>
After working the operate, the aforementioned component’s markup will flip into this:
<a href="" data-split data-split-type="hover"> <span class="internal"> <span class="entrance"> <span class="char" model="--index: 1;">H</span> <span class="char" model="--index: 2;">o</span> <span class="char" model="--index: 3;">v</span> <span class="char" model="--index: 4;">e</span> <span class="char" model="--index: 5;">r</span> <span> </span> <span class="char" model="--index: 6;">m</span> <span class="char" model="--index: 7;">e</span> <span> </span> <span class="char" model="--index: 8;">→</span> </span> <span class="again"> <span class="char" model="--index: 1;">H</span> <span class="char" model="--index: 2;">o</span> <span class="char" model="--index: 3;">v</span> <span class="char" model="--index: 4;">e</span> <span class="char" model="--index: 5;">r</span> <span> </span> <span class="char" model="--index: 6;">m</span> <span class="char" model="--index: 7;">e</span> <span> </span> <span class="char" model="--index: 8;">→</span> </span> </span> </a>
The tip outcome will seem the identical as earlier than working the operate.
Right here are some things to notice:
- Contained in the goal component we’ll outline the
.internal
component which can include the.entrance
and.again
parts. We add this wrapperspan
to isolate the types of the goal component (e.g. hyperlink) and keep away from any inconsistencies (e.g. if we add paddings to the hyperlink). - Each the
.entrance
and.again
parts will include the component’s preliminary textual content wrapped intospan
parts. - By default, the contents of the
.entrance
component can be seen. Relying on the animation sort, the contents of the.again
one will seem when both we hover over the goal component or scroll until it comes into view. - As we’ll see in a bit, we’ll use the
transition-delay
property to sequentially animate the characters. To create completely different delays between them, we’ll use theindex
CSS variable. Every character, aside from the empty one (whitespace), will obtain as a worth of this variable an incremented quantity which can denote the character’s place/index inside their mum or dad.
With all these in thoughts, right here’s the entire declaration of the splitCharacters()
operate:
operate splitCharacters() { const targets = doc.querySelectorAll("[data-split]"); for (const goal of targets) { let string = '<span class="internal"><span class="entrance"https://webdesign.tutsplus.com/tutorials/>"; let counter = 0; const targetContent = goal.textContent; const phrases = targetContent.trim().cut up(" "); phrases.forEach(operate (phrase, wordIndex, wordArray) { const chars = phrase.cut up(""); chars.forEach(operate (char, charIndex, charArray) { string += `<span class="char" model="--index: ${++counter};">${char}</span>`; if ( wordIndex === wordArray.size - 1 && charIndex === charArray.size - 1 ) { counter = 0; } }); if (wordIndex !== wordArray.size - 1) { string += "<span> </span>"; } }); string += "</span>"; string += '<span class="again"https://webdesign.tutsplus.com/tutorials/>"; phrases.forEach(operate (phrase, wordIndex, wordArray) { const chars = phrase.cut up(""); chars.forEach(operate (char) { string += `<span class="char" model="--index: ${++counter};">${char}</span>`; }); if (wordIndex !== wordArray.size - 1) { string += "<span> </span>"; } }); string += "</span>"; string += "</span>"; goal.innerHTML = string; } }
3. Add Types
For simplicity, we’ll solely focus our consideration on the principle types. Apart from, you’ll be able to verify all of them by clicking on the CSS tab of the demo.
Listed here are the noteworthy issues:
- The
.again
component can be an absolute component. - By default, there can be a 0.015s distinction between the animation of every character. That mentioned, the primary character of the
.entrance
and.again
parts may have a transition delay of 0.015s, the second 0.03s, the third one 0.045s, and so forth. The whitespace gained’t have any delay. - The characters contained in the
.again
component can be hidden by default and sit beneath the textual content like this:
The related types:
[data-split], [data-split] span { show: inline-block; } [data-split] .internal { show: block; place: relative; overflow: hidden; } [data-split] .again { place: absolute; prime: 0; proper: 0; backside: 0; left: 0; } [data-split] .char { transition: all 0.4s cubic-bezier(0.2, 0.63, 0.4, 1.02); transition-delay: calc(0.015s * var(--index)); } [data-split] .again .char { opacity: 0; rework: translateY(101%) skewX(55deg); } [data-split-type="hover"] .char { transition-duration: 0.25s; }
Hover Animation
As we hover over a component with the [data-split-type="hover"]
attribute, the characters of the .again
component will seem whereas those of the .entrance
component will develop into hidden by transferring upwards like this:
Listed here are the corresponding types:
[data-split-type="hover"]:hover .again .char { opacity: 1; rework: none; } [data-split-type="hover"]:hover .entrance .char { opacity: 0; rework: translateY(-101%) skewX(-55deg); }
4. Animate on Scroll
As we scroll the web page, all parts with the [data-split-type="scroll"]
attribute can be animated as quickly as they develop into seen within the viewport. In our instance, solely the headings may have this conduct.
To perform this activity, we’ll borrow some code from this tutorial that makes use of the Intersection Observer API.
So, when at the very least 50% of every heading comes into view, it’ll obtain the is-animated
class. In any other case, it’ll lose this class, and the animation will return to its preliminary state.
Under we declare the operate accountable for these things:
animateOnScroll(); operate animateOnScroll() { const targets = doc.querySelectorAll('[data-split-type="scroll"]'); const isAnimatedClass = "is-animated"; const threshold = 0.5; operate callback(entries, observer) { entries.forEach((entry) => { const elem = entry.goal; if (entry.intersectionRatio >= threshold) { elem.classList.add(isAnimatedClass); } else { elem.classList.take away(isAnimatedClass); } }); } const observer = new IntersectionObserver(callback, { threshold }); for (const goal of targets) { observer.observe(goal); } }
And listed below are the types that kick in throughout this situation:
[data-split-type="scroll"].is-animated .again .char { opacity: 1; rework: none; } [data-split-type="scroll"].is-animated .entrance .char { opacity: 0; rework: translateY(-101%) skewX(-55deg); }
Conclusion
That’s all for right now, of us! Throughout this train, we coated a option to cut up a component’s textual content into particular person characters and animate them on scroll and hover. I hope you gained some new data that you just’ll use to reinforce this demo or create related textual content results in your tasks. In that case, don’t overlook to present our demo some love 🙂
Let’s have a look at our creation as soon as once more:
In case you want a extra full and sturdy answer to animate phrases, characters, strains, and so forth. you’ll be able to strive a JavaScript library like Splitting.js or GSAP’s SplitText.js (though it isn’t free).
As at all times, thanks loads for studying!