Jump to content
Search Community

Letters Animate On Scroll and also collapse

shahbazliaquat test
Moderator Tag

Recommended Posts

Hello there, I want to make animation in gsap which I want to follow but still can't achieve by doing multiple things and coding,

 

When I scroll letters animate,

every letter has its animation

they collapse with each other while animating,

 

here is the reference website that I want to make like that... https://raxo.co/

 

check the hero section on this website, I want the same as this...

 

if any body helps me with this, I am very grateful. Thanks

Link to comment
Share on other sites

We love helping with GSAP-related questions, but unfortunately we just don't have the resources to provide free general consulting, logic troubleshooting, or "how do I recreate this cool effect I saw on another site?" tutorials. Of course anyone else is welcome to post an answer - we just want to manage expectations.  

 

What have you tried already? We love to see minimal demo's, that way we can see your thought process and thus better help you understand the tools!


If you're new to GSAP check out this awesome getting started guide https://gsap.com/resources/get-started/

 

You are welcome to post in the "Jobs & Freelance" forum for paid consulting, or contact us directly. 

Link to comment
Share on other sites

Hi,

 

Actually if you inspect that section of the page you'll see that is a lottie animation:

i51IqqO.png

Is always a good idea to inspect the HTML with dev tools in order to know a bit more about how things are being done in a specific page, that is one of the best tools in a developers' tools set.

 

Another approach would be to use matter js:

https://brm.io/matter-js/

 

Good luck with your project!

Happy Tweening!

Link to comment
Share on other sites

  • 2 weeks later...

Thank you @Rodrigo I did my complete research, and when I felt helpless with any animation then I came to communities regarding my questions,

 

I checked all possible ways, So I did this work 80% the best way I use, use splittext, and then select every word individually in JS and apply animations on every individual element so that they behave individually like I mentioned example in my question,

 

But,

 

But, one thing that I want to do, and I can't do that, is .... see (in example website) how words animate when they collapse with each other, and animate according to that collision,

 

Yes I make animation nearly the same, but I couldn't apply collision and after deep research from my side I can't understand how I do this with minimum code (because our end goal is always to make the site speedy,)

 

I will mention what I did yet in my next answer by attaching codepen,

 

I just want to make words animate on collision, that overall thing is...

Link to comment
Share on other sites

Hi,

 

Collision detection and physics momentum is not what GSAP was made for, the closest tool we have for that are the Physics2D and PhysicsProps Plugins:

https://gsap.com/docs/v3/Plugins/Physics2DPlugin

https://gsap.com/docs/v3/Plugins/PhysicsPropsPlugin

 

Beyond that you'll have to come up with the custom logic for that. @OSUblake came up with this simple approach a few years ago:

See the Pen fd96fdd774bb202254adefed93916d8a by osublake (@osublake) on CodePen

 

Maybe that can help somehow.

Other than that I know nothing on the subject so you should be looking in google or youtube to see how other developers approach this.

 

Happy Tweening!

Link to comment
Share on other sites

  • 2 months later...

Hi! I want to create the same animation got inspo from the same website! Did you figure it out? I have been working with framer motion and matter js and got a bunch of cool text animations but not quite there yet with the desired outcome. 

Link to comment
Share on other sites

9 hours ago, Betts said:

Hi! I want to create the same animation got inspo from the same website! Did you figure it out? I have been working with framer motion and matter js and got a bunch of cool text animations but not quite there yet with the desired outcome. 

Can you please be more specific? Did you have a GSAP-specific question we can help you with? Got a minimal demo illustrating the issue you're struggling with? 

Link to comment
Share on other sites

On 5/25/2024 at 2:14 PM, Betts said:

Hi! I want to create the same animation got inspo from the same website! Did you figure it out? I have been working with framer motion and matter js and got a bunch of cool text animations but not quite there yet with the desired outcome. 

bro I do this with simple JS, yeah it takes my 4 to 5 days to understand things and finally i do this in simple js with GSAP ScrollTrigger

Link to comment
Share on other sites

Yeah I got the letters moving up in a similar way but i wanted the collisions to work too but as mentioned before gsap does not seem the indicated for this. 
I a few things, the one that was closest was having a func to calculate the letter boundaries, once a letter boundary collided with another depending on the intersection of the collision rotate or not rotate the letter and then push the letter down or to the right. However the collisions felt very artificial if it makes sense so I decided to just keep the original simple scrollTrigger code from gsap. 

I'll put this on a codepen

import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { MutableRefObject, useEffect, useRef } from 'react';

gsap.registerPlugin(ScrollTrigger);

const creativity = 'Creativity ';
const is = 'is ';
const my = 'my ';
const craft = 'craft';

type LetterBounds = {
  left: number;
  right: number;
  top: number;
  bottom: number;
};

type LetterPositions = {
  x: number;
  y: number;
  rotation: number;
  speed: number;
};

function getRandomSpeed() {
  const randomDecimal = Math.random();
  return 0.5 + randomDecimal * (1 - 0.6);
}

const creativityPositions: LetterPositions[] = [
  { x: 8, y: 100, rotation: -3, speed: 0.3 }, // C
  { x: 5, y: 200, rotation: 2, speed: 0.3 }, // r
  { x: 21, y: 150, rotation: -10, speed: 0.1 }, // e
  { x: 16, y: 258, rotation: 40, speed: 0.5 }, // a
  { x: 15, y: 96, rotation: -20, speed: 0.3 }, // t
  { x: 10, y: 412, rotation: 30, speed: 0.2 }, // i
  { x: 33, y: 353, rotation: -15, speed: 0.1 }, // v
  { x: 9, y: 368, rotation: 25, speed: 0.4 }, // i
  { x: 11, y: 128, rotation: -35, speed: 0.2 }, // t
  { x: 25, y: 300, rotation: 15, speed: 0.4 } // y
];
const isPositions: LetterPositions[] = [
  { x: 17, y: 150, rotation: -25, speed: 0.3 }, // i
  { x: -13, y: -325, rotation: 35, speed: 0.2 } // s
];
const myPositions: LetterPositions[] = [
  { x: 26, y: 100, rotation: -20, speed: 0.3 }, // m
  { x: 22, y: -27, rotation: 30, speed: 0.4 } // y
];
const craftPositions: LetterPositions[] = [
  { x: 12, y: -250, rotation: 20, speed: 0.3 }, // c
  { x: 10, y: -409, rotation: -30, speed: 0.5 }, // r
  { x: -10, y: -202, rotation: 0, speed: 0.3 }, // a
  { x: 53, y: -357, rotation: -10, speed: 0.6 } // f
];

function getLetterBounds(letter: Element): LetterBounds {
  const letterBounds = letter.getBoundingClientRect();
  return {
    top: letterBounds.top,
    left: letterBounds.left,
    right: letterBounds.right,
    bottom: letterBounds.bottom
  };
}

function isRectanglesColliding(
  rect1: LetterBounds,
  rect2: LetterBounds
): boolean {
  return (
    rect1.left < rect2.right &&
    rect1.right > rect2.left &&
    rect1.top < rect2.bottom &&
    rect1.bottom > rect2.top
  );
}

const animateLettersOnScroll = (containerRef: MutableRefObject<any>) => {
  const lettersContainer = containerRef.current;
  const letterElements = lettersContainer?.querySelectorAll('.letter');

  letterElements.forEach((letter: Element, index: number) => {
    gsap.to(letter, {
      y: (i, el) =>
        (1 - parseFloat(el.getAttribute('data-speed'))) *
        ScrollTrigger.maxScroll(window),
      ease: 'none',
      scrollTrigger: {
        start: 0,
        end: 'max',
        invalidateOnRefresh: true,
        scrub: 0
      },
      rotation: creativityPositions[index]?.rotation ?? 10
    });
  });
};

function handleCollisions(containerRef: MutableRefObject<any>) {
  const lettersContainer = containerRef.current;
  const letterElements = lettersContainer?.querySelectorAll('.letter');

  if (letterElements) {
    const letterBoxes: { [key: string]: LetterBounds } = {};

    letterElements.forEach((letter: Element) => {
      letterBoxes[letter.textContent || ''] = getLetterBounds(letter);
    });

    letterElements.forEach((letter: Element) => {
      const letterBox = letterBoxes[letter.textContent || ''];

      if (!letterBox) return;

      Object.entries(letterBoxes).forEach(([otherLetter, otherBox]) => {
        if (otherLetter === letter.textContent) return;

        if (isRectanglesColliding(letterBox, otherBox)) {
          const dx = otherBox.left - letterBox.left;
          const dy = otherBox.top - letterBox.top;
          const angle = Math.atan2(dy, dx);
          const newX = Math.cos(angle) * 10;
          const newY = Math.sin(angle) * 10;

          gsap.to(letter, {
            duration: 0.8,
            x: `+=${newX}`,
            y: `+=${newY}`,
            rotation: `+=${angle * (3 / Math.PI)}`
          });
        }
      });
    });
  }
}

function LetterDisplay({ word }: { word: string }) {
  return word.split('').map((letter, index) => (
    <div
      key={index}
      className="letter xs:leading-none xs:text-[90px] text-6xl font-semibold md:text-[120px] lg:text-[150px] xl:text-[210px] "
      data-speed={getRandomSpeed()}
    >
      {letter}
    </div>
  ));
}

export function LetterCollision() {
  const containerRef = useRef(null);

  useEffect(() => {
    animateLettersOnScroll(containerRef);

    // const collisionInterval = setInterval(() => {
    //   handleCollisions(containerRef);
    // }, 100);

    // return () => {
    //   clearInterval(collisionInterval);
    // };
  }, []);

  return (
    <div
      ref={containerRef}
      className="mt-16 flex flex-col justify-end lg:mt-80 lg:h-screen xl:mt-[60vh]"
    >
      <div className="flex flex-wrap p-0">
        <LetterDisplay word={creativity} />
        <div className="xs:w-4 w-2 sm:w-10"></div>
        <LetterDisplay word={is} />
      </div>
      <div className="flex flex-wrap">
        <LetterDisplay word={my} />
        <div className="xs:w-4 w-2 sm:w-10"></div>
        <LetterDisplay word={craft} />
      </div>
    </div>
  );
}

 

Link to comment
Share on other sites

Proper cleanup is very important with frameworks, but especially with React. React 18 runs in strict mode locally by default which causes your useEffect() and useLayoutEffect() to get called TWICE.

 

Since GSAP 3.12, we have the useGSAP() hook (the NPM package is here) that simplifies creating and cleaning up animations in React (including Next, Remix, etc). It's a drop-in replacement for useEffect()/useLayoutEffect(). All the GSAP-related objects (animations, ScrollTriggers, etc.) created while the function executes get collected and then reverted when the hook gets torn down.

 

Here is how it works:

const container = useRef(); // the root level element of your component (for scoping selector text which is optional)

useGSAP(() => {
  // gsap code here...
}, { dependencies: [endX], scope: container }); // config object offers maximum flexibility

Or if you prefer, you can use the same method signature as useEffect():

useGSAP(() => {
  // gsap code here...
}, [endX]); // simple dependency Array setup like useEffect()

This pattern follows React's best practices.

 

We strongly recommend reading the React guide we've put together at https://gsap.com/resources/React/

 

If you still need help, here's a React starter template that you can fork to create a minimal demo illustrating whatever issue you're running into. Post a link to your fork back here and we'd be happy to take a peek and answer any GSAP-related questions you have. Just use simple colored <div> elements in your demo; no need to recreate your whole project with client artwork, etc. The simpler the better. 

Link to comment
Share on other sites

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...