Jump to content
Search Community

SplitText animation whilst pinning

NickWoodward
Moderator Tag

Go to solution Solved by Sam Tremblay,

Recommended Posts

NickWoodward
Posted

Hello! Couple of questions if someone has a sec or two please?😊

https://codesandbox.io/p/devbox/7p786f

Is there a better way to sync the masking reveal of the text with the pinning here? Obviously it's a bit of a matter of taste, but maybe with the duration of the pinning, or so that at least the reveal happens after the pinning? I've had a bit of a play around, but I'm just mindful of learning bad habits.

Also - the responsive splitting - the background text is obviously responsive, but the split text isn't. The docs reference autosplit, but I'm a little confused how to create the splitting animation in the onSplit callback if it's part of a larger timeline? I'm also seeing a lot of the bug in the screenshot - where resizing makes content and triggers 'jump'. I'm just not sure how to debug them tbh

Hope I've explained that well enough!

Nickimage.thumb.png.047583325fb190163e789a0b15f4043f.png

Sam Tremblay
Posted

Hello!

 

Instead of calculating each line, you could use background-clip: text on each line, or set up mix-blend-mode with layered elements at different depths. Then, you can animate the background clip using a GSAP stagger based on the section, or apply a specific translate or scale to a div if you go with the mix-blend-mode approach.

  • Solution
Sam Tremblay
Posted

Hello again!

 

I do an example for you with background-clip: text.

 

You can inspect behavior here: https://studiochampgauche.com/demo/

 

Here is my JS:

'use strict';
import React, { useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { SplitText } from 'gsap/SplitText';
import Image from './components/Image';

const Demo = () => {

	const ref = useRef(null);
	const innerRef = useRef(null);
	const containerRef = useRef(null);
	const textRef = useRef(null);
	const paragraphRef = useRef([]);

	useEffect(() => {

		gsap.registerPlugin(SplitText);

		let killEvents = [];

		
		/*
		* Pin inner element
		*/
		let pinAnimation = ScrollTrigger.create({
			trigger: ref.current,
			start: 'top top',
			end: 'bottom bottom',
			pin: innerRef.current,
			pinSpacing: false,
			scrub: true
		});


		/*
		* Split Text
		*/
		let split = SplitText.create(paragraphRef.current, {
			type: 'lines',
			linesClass: 'line',
		});


		/*
		* Anim split
		*/
		let splitAnimation = gsap.to([paragraphRef.current[0].querySelectorAll('.line'), paragraphRef.current[1].querySelectorAll('.line')], 1, {
			backgroundPositionY: 0,
			stagger: .6,
			ease: 'none',
			scrollTrigger: {
				trigger: ref.current,
				start: 'top top',
				end: 'bottom bottom',
				scrub: true
			}
		});


		killEvents.push(() => {

			if(pinAnimation){

				pinAnimation.kill();
				pinAnimation = null;

			}


			if(split){

				split.revert();
				split = null;

			}


			if(splitAnimation){

				splitAnimation.revert();
				splitAnimation = null;

			}

		});


		return () => {

			killEvents?.forEach(killEvent => killEvent());

			killEvents = [];

		}

	});

	return(
		<>
			<section ref={ref} id="demo-1">
				<div ref={innerRef} className="inner">
					<div ref={containerRef} className="container">
						<div ref={textRef} className="text">
							<p ref={el => paragraphRef.current[0] = el}>Lorem ipsum dolor sit amet consectetur adipisicing elit. Excepturi laborum repellendus.</p>

							<p ref={el => paragraphRef.current[1] = el}>Rerum assumenda dolore neque mollitia praesentium adipisci amet commodi sint aliquid pariatur.</p>
						</div>
					</div>
				</div>
			</section>
			<section className="fake" style={{
				height: '100svh'
			}}>
				<div className="container">
					<p>Silence is golden</p>
				</div>
			</section>
		</>
	);
	
}

export default Demo;

 

Here is my SCSS:

//@use '../inc/variables' as *;
//@use '../inc/functions' as *;
//@use '../inc/mixins' as *;

#demo-1{
	height: 300svh;
	padding: 0 30px;
	.inner{
		display: flex;
		height: 100svh;
		align-items: center;
		justify-content: center;
		will-change: transform;
		.container{
			width: 100%;
			max-width: 1640px;
			.text{
				width: 100%;
				margin: 0 auto;
				max-width: 1088px;
				p{
					font-size: 45px;
					line-height: 140%;
					//color: transparent;
					.line{
						position: relative;
						background-image: linear-gradient(to top, #ff0000 50%, #00ff00 0);
						background-position-y: 100%;
						background-size: 100% 200%;
						background-clip: text;
						color: transparent;
						.word{
							.char{

							}
						}
					}
					& + p{
						margin: 25px 0 0;
					}
				}
			}
		}
	}
}

 

  • Like 1
NickWoodward
Posted

Cool effect - I never would have thought of doing it that way. Like how it also handles multiple paragraphs. Thanks!

I assume with useGSAP I don't have to set up that killEvents array?

And for responsiveness I'd just have to use a screen width value in the useEffect/GSAP dependency array?

Thanks again!

Sam Tremblay
Posted

Thanks chief!

 

About useGSAP, I don't know because I use all time useEffect. I never install the react part of GSAP.

 

And for the responsive, it's depend. You have multiple ways. I love use px/vw/vh units directly in CSS (with clamp & some breakpoints) and gsap.matchMedia in JavaScript for start or kill some animations based on media query.

  • Like 1
  • 3 weeks later...
NickWoodward
Posted

Sorry to bother you again @Sam Tremblay, but I don't suppose you can see why my implementation of your effect isn't animating please? (it's instantly switching the background position, for all lines). I just can't see why it's not working, looks functionally the same to me?

Your working code example: https://codesandbox.io/p/devbox/fkgtrg
Mine: https://codesandbox.io/p/devbox/creed-hero-forked-pzsnfr?workspaceId=ws_3p4VjtDpEFGetZAxu6vymW


 

NickWoodward
Posted

ahhh, for some reason - that I haven't yet worked out - your scrolltrigger is twice the size as mine. 

if i add "+=20%" to my 'end' - end: "bottom+=20% bottom" it works, but I don't really know why. I mean it works!😄

Sam Tremblay
Posted

Hey @NickWoodward!

I'm not sure to understand the problem, my english is not super good hehe. I see you use +=100% now instead of 20%.... Maybe you need that because you have the pinSpacing to true. Try to turn this parameter to false.

 

Does it help?

 

 

EDIT: hmmmm... Or maybe it's because I have a pinned container 100vh moving in a div 300vh and not you ?

  • Like 1
NickWoodward
Posted
7 hours ago, Sam Tremblay said:

maybe it's because I have a pinned container 100vh moving in a div 300vh and not you

yeah i did think it might be this.

it's ok though, i've got it working well now, with the autoSplit/onSplit callback. thanks a lot for your help. your english seems great btw 🙂

  • Like 1
Sam Tremblay
Posted

Hehe! Awesome 🥳

 

Thanks for my english!

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...