Codrops 원본

On-Scroll Typography Animations

텍스트 · MIT

텍스트 더 보기
ORIGINAL PREVIEW
On-Scroll Typography Animations 정적 미리보기

갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.

큰 화면으로 보기 새 탭

SOURCE FILES

원본 코드 읽기

9개 파일

수집한 원본 소스와 실행 안내를 함께 제공합니다.

src/css/base.css
파일 저장

*,
*::after,
*::before {
	box-sizing: border-box;
}

:root {
	font-size: 16px;
	--color-text: #fff;
	--color-bg: rgb(71,50,171);
	--color-link: #fff;
	--color-link-hover: #fff;
	--page-padding: 1.5rem 2rem;
}

body {
	margin: 0;
	color: var(--color-text);
	background-color: var(--color-bg);
	font-family: area-normal,-apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	overflow-x: hidden;
}

.demo-1 {
	background-image: linear-gradient(
	  180deg,
	  hsl(240deg 91% 49%) 0%,
	  hsl(252deg 93% 47%) 10%,
	  hsl(258deg 93% 46%) 20%,
	  hsl(263deg 93% 46%) 30%,
	  hsl(266deg 91% 45%) 40%,
	  hsl(269deg 88% 45%) 50%,
	  hsl(272deg 78% 43%) 60%,
	  hsl(275deg 69% 40%) 70%,
	  hsl(277deg 60% 37%) 80%,
	  hsl(294deg 74% 43%) 90%, 
	  hsl(258deg 76% 45%) 100%
	);
}

.demo-2 {
	--color-text: #ddc4a4;
    --color-bg: #0e0e0e;
    --color-link: #ede3e3;
    --color-link-hover: #d68521;
}

/* Page Loader */
.js .loading::before,
.js .loading::after {
	content: '';
	position: fixed;
	z-index: 1000;
}

.js .loading::before {
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background: var(--color-bg);
}

.js .loading::after {
	top: 50%;
	left: 50%;
	width: 60px;
	height: 60px;
	margin: -30px 0 0 -30px;
	border-radius: 50%;
	opacity: 0.4;
	background: var(--color-link);
	animation: loaderAnim 0.7s linear infinite alternate forwards;

}

@keyframes loaderAnim {
	to {
		opacity: 1;
		transform: scale3d(0.5,0.5,1);
	}
}

a {
	text-decoration: none;
	color: var(--color-link);
	outline: none;
	cursor: pointer;
}

a:hover {
	color: var(--color-link-hover);
	outline: none;
}

/* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
a:focus {
	/* Provide a fallback style for browsers
	 that don't support :focus-visible */
	outline: none;
	background: lightgrey;
}

a:focus:not(:focus-visible) {
	/* Remove the focus indicator on mouse-focus for browsers
	 that do support :focus-visible */
	background: transparent;
}

a:focus-visible {
	/* Draw a very noticeable focus style for
	 keyboard-focus on browsers that do support
	 :focus-visible */
	outline: 2px solid red;
	background: transparent;
}

.unbutton {
	background: none;
	border: 0;
	padding: 0;
	margin: 0;
	font: inherit;
	cursor: pointer;
}

.unbutton:focus {
	outline: none;
}

main {
	counter-reset: section;
}

.splitting .word {
	white-space: nowrap;
}

.frame {
	color: var(--color-title);
	padding: var(--page-padding);
	display: grid;
	grid-template-columns: 1fr;
	grid-template-rows: auto auto auto auto;
	grid-template-areas: 'title' 'prev' 'demos' 'sponsor';
	justify-content: start;
	justify-items: end;
	margin-bottom: 3rem;
	grid-gap: 0.5rem;
	align-items: start;
	font-weight: 600;
	font-size: 14px;
}

.frame a:not(.frame__title-back),
.line {
	white-space: nowrap;
	overflow: hidden;
	position: relative;
}

.frame a:not(.frame__title-back)::before,
.line::before {
	content: '';
	height: 1px;
	width: 100%;
	background: currentColor;
	position: absolute;
	top: 90%;
	transition: transform 0.3s;
	transform-origin: 0% 50%;
}

.frame a:not(.frame__title-back):hover::before,
.line:hover::before {
	transform: scaleX(0);
	transform-origin: 100% 50%;
}

.frame__title {
	grid-area: title;
	display: flex;
	align-items: center;
}

.frame__title-main {
	font-size: 1rem;
	margin: 0;
	font-weight: normal;
}

.frame__title-back {
	position: relative;
	display: flex;
	align-items: flex-end;
}

.frame__title-back svg {
	fill: currentColor;
}

.frame__prev {
	grid-area: prev;
	align-self: start;
}

.frame__demos {
	display: flex;
	gap: 1rem;
}

.frame__demos span {
	opacity: 0.7;
}

.intro {
	height: 100vh;
	display: grid;
	place-items: center;
	text-align: center;
}

.intro__title {
	display: grid;
	place-items: center;
	margin: 0;
	line-height: 0.9;
	font-weight: 300;
}

.intro__title-pre {
	font-family: norman-stencil, sans-serif;
	font-weight: 800;
	text-transform: uppercase;
	font-size: clamp(2rem,10vw,9rem);
}

.intro__title-sub {
	font-size: clamp(1rem,6vw,2.75rem);
}

.intro__info {
	position: relative;
	max-width: 20ch;
	opacity: 0.6;
	line-height: 1.2;
	margin-bottom: 4rem;
	padding-bottom: 1rem;
	font-weight: 600;
	font-size: 14px;
}

.intro__info::after {
	content: '';
	position: absolute;
	left: 50%;
	width: 1px;
	height: 2rem;
	background: #fff;
	top: 100%;
}

.content {
	display: flex;
	flex-direction: column;
	width: 100vw;
	position: relative;
	padding: var(--page-padding);
}

.content:last-child {
	padding-bottom: 40vh;
}

.content--full {
	padding: 0;
}

.content p {
	max-width: 660px;
	margin: 1.5rem auto;
	font-size: 1.25rem;
	line-height: 1.5;
	font-weight: 600;
}

.content--highlight p {
	font-size: clamp(1rem,3vw,2rem);
}

.content__title {
	font-size: 12vw;
	line-height: 0.8;
	counter-increment: section;
	text-align: center;
	display: grid;
	gap: 2rem;
}

.content__title--left {
	text-align: left;
}

.content__title--right {
	text-align: right;
}

.content__title::before {
	content: '.'counter(section);
	font-weight: 400;
	font-family: area-normal, sans-serif;
	display: block;
	font-size: 1rem;
	margin-bottom: 10vh;
	text-align: center;
}

.content__title--sides {
	display: grid;
	grid-template-rows: auto auto;
	grid-template-columns: 100%;
	text-align: left;
	gap: 2rem;
}

.content__title--sides span:last-child {
	justify-self: end;
}

.font-1 {
	font-family: wayfinder-cf, serif;
	font-weight: 100;
}

.font-2 {
	font-family: campaign-serif, sans-serif;
	font-weight: 400;
}

.font-3 {
	font-family: area-normal, sans-serif;
	font-weight: 600;
}

.font-4 {
	font-family: campaign-serif, sans-serif;
	font-weight: 400;
	font-style: italic;
}

.font-5 {
	font-family: zedou, sans-serif;
	font-weight: 300;
	text-transform: uppercase;
}

.font-6 {
	font-family: afronaut, sans-serif;
	font-weight: 400;
}

.font-7 {
	font-family: hoss-round-wide, sans-serif;
	font-weight: 400;
}

.font-8 {
	font-family: monotalic, sans-serif;
	font-weight: 400;
	text-transform: lowercase;
}

.font-9 {
	font-family: aziga, sans-serif;
	font-weight: 400;
}

.font-10 {
	font-family: norman-stencil, sans-serif;
	font-weight: 800;
}

.font-11 {
	font-family: neighbor, sans-serif;
	font-weight: 400;
}

.font-12 {
	font-family: halogen, sans-serif;
	font-weight: 700;
}

.font-13 {
	font-weight: initial;
	font-family: "marlide-display-variable", sans-serif;
	font-variation-settings: "wght" 400;
}

.font-14 {
	font-weight: initial;
	font-family: "june-expt-variable", sans-serif;
	font-variation-settings: "STYL" 0;
}

.font-15 {
	font-weight: initial;
	font-family: "june-expt-variable",sans-serif;
	font-variation-settings: 'STYL' 100;
}

.font-16 {
	font-weight: initial;
	font-family: "inge-variable", sans-serif;
	font-variation-settings: "slnt" 0;
}

.font-17 {
	font-weight: initial;
	font-family: "antipoda-variable", sans-serif;
	font-variation-settings: "wght" 450, "wdth" 80;
}

.font-18 {
	font-weight: initial;
	font-family: "vinila-variable", sans-serif;
	font-variation-settings: "slnt" 0, "wght" 100, "wdth" 100;
}

.font-19 {
	font-weight: initial;
	font-family: "nagel-variable", sans-serif;
	font-variation-settings: "slnt" 0, "wght" 610;
}

.font-20 {
	font-weight: initial;
	font-family: "borax-variable", sans-serif;
	font-variation-settings: "wght" 400;
}

.font-21 {
	font-family: "gigantic", sans-serif;
	font-weight: 900;
	font-style: normal;
}

.font-22 {
	font-family: "capsule", sans-serif;
	font-weight: 400;
	font-style: normal;
}

.font-23 {
	font-family: "arsenica-variable", sans-serif;
	font-variation-settings: "opsz" 1000, "wght" 300;
}

.font-height {
	line-height: 1.4;
}

.font-height-medium {
	line-height: 0.9;
}

.font-medium {
	font-size: 6vw;
	margin-top: 3rem;
}

.font-larger {
	font-size: 27vw;
}

.font-upper {
	text-transform: uppercase;
}

.font-sub {
	font-size: 5vw;
	margin-top: 2rem;
    display: block;
}

.splitting .font-sub .word {
	display: grid;
	grid-auto-flow: column;
}

.char-wrap {
	display: inline-block;
	position: relative;
	overflow: hidden;
}

@media screen and (min-width: 53em) {
	.frame {
		justify-items: start;
		grid-gap: 2rem;
		grid-template-columns: auto auto 1fr auto;
		grid-template-rows: auto;
		grid-template-areas: 'title prev demos sponsor';
	}
	.frame__demos {
		justify-self: end;
	}
}
src/index.html
파일 저장

<!DOCTYPE html>
<html lang="en" class="no-js">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>On-Scroll Typygorahy Animations | Set 1 | Codrops</title>
    <meta name="description" content="Some inspiration for text animations on scroll." />
    <meta name="keywords" content="typography, animation, on-scroll, gsap, ScrollTrigger" />
    <meta name="author" content="Codrops" />
    <link rel="shortcut icon" href="favicon.ico">
    <link rel="stylesheet" href="https://use.typekit.net/cvn8slu.css">
    <link rel="stylesheet" type="text/css" href="css/base.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
    <script>
    document.documentElement.className = "js";
    var supportsCssVars = function() { var e, t = document.createElement("style"); return t.innerHTML = "root: { --tmp-var: bold; }", document.head.appendChild(t), e = !!(window.CSS && window.CSS.supports && window.CSS.supports("font-weight", "var(--tmp-var)")), t.parentNode.removeChild(t), e };
    supportsCssVars() || alert("Please view this demo in a modern browser that supports CSS Variables.");
    </script>
    <script src="//tympanus.net/codrops/adpacks/analytics.js"></script>
</head>

<body class="demo-1 loading">
    <main>
        <div class="frame">
            <div class="frame__title">
                <a aria-label="Back to the article" class="frame__title-back" href="https://tympanus.net/codrops/?p=69734">
                    <span>Back to the article</span>
                    <svg width="18px" height="18px" viewBox="0 0 24 24">
                        <path vector-effect="non-scaling-stroke" d="M18.25 15.5a.75.75 0 00.75-.75v-9a.75.75 0 00-.75-.75h-9a.75.75 0 000 1.5h7.19L6.22 16.72a.75.75 0 101.06 1.06L17.5 7.56v7.19c0 .414.336.75.75.75z"></path>
                    </svg>
                </a>
            </div>
            <a class="frame__prev" href="https://tympanus.net/Tutorials/LoopScrolling/">Previous demo</a>
            <nav class="frame__demos">
                <span>Set 1</span>
                <a href="index2.html">Set 2</a>
            </nav>
        </div>
        <div class="intro">
            <h1 class="intro__title">
                <span class="intro__title-pre">On-Scroll</span>
                <span class="intro__title-sub">Typography Animations</span>
            </h1>
            <span class="intro__info">Please scroll moderately to fully experience the animations</span>
        </div>
        <div class="content content--highlight">
            <p>My legacy &#x2014;<br>
                What will it be?<br>
                Flowers in spring,<br>
                The cuckoo in summer,<br>
                And the crimson maples<br>
                Of autumn...</p>
        </div>
        <div class="content">
            <h2 class="content__title content__title--small" data-splitting data-effect1><span>Beyond</span><span class="font-6">meaning</span></h2>
            <p>Studying texts and stiff meditation can make you lose your Original Mind.
                A solitary tune by a fisherman, though, can be an invaluable treasure.
                Dusk rain on the river, the moon peeking in and out of the clouds;
                Elegant beyond words, he chants his songs night after night.</p>
            <p>This treasure was discovered in a bamboo thicket &mdash;
                I washed the bowl in a spring and then mended it.
                After morning meditation, I take my gruel in it;
                At night, it serves me soup or rice.
                Cracked, worn, weather-beaten, and misshapen
                But still of noble stock!</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect2><span class="font-2">Heavenly</span><span class="font-4">pleasure</span></h2>
        </div>
        <div class="content">
            <p>I watch people in the world<br>
                Throw away their lives lusting after things,<br>
                Never able to satisfy their desires,<br>
                Falling into deeper despair<br>
                And torturing themselves.<br>
                Even if they get what they want<br>
                How long will they be able to enjoy it?<br>
                For one heavenly pleasure<br>
                They suffer ten torments of hell,<br>
                Binding themselves more firmly to the grindstone.<br>
                Such people are like monkeys<br>
                Frantically grasping for the moon in the water<br>
                And then falling into a whirlpool.<br>
                How endlessly those caught up in the floating world suffer.<br>
                Despite myself, I fret over them all night<br>
                And cannot staunch my flow of tears.</p>
        </div>
        <div class="content">
            <h2 class="content__title content__title--sides" data-splitting data-effect3><span class="font-7">The blossoms</span><span class="font-1">have fallen</span></h2>
        </div>
        <div class="content">
            <p>The world before my eyes is wan and wasted, just like me.
                The earth is decrepit, the sky stormy, all the grass withered.
                No spring breeze even at this late date,
                Just winter clouds swallowing up my tiny reed hut.</p>
            <p>The wind has settled, the blossoms have fallen;
                Birds sing, the mountains grow dark &mdash;
                This is the wondrous power of Buddhism.</p>
        </div>
        <div class="content content--full">
            <h2 class="content__title" data-splitting data-effect4><span class="font-larger">Human</span><span class="font-upper font-sub font-7">gratitude</span></h2>
        </div>
        <div class="content">
            <p>The tree was barren of leaves but you brought a new spring.
                Long green sprouts, verdant flowers, fresh promise.
                Mori, if I ever forget my profound gratitude to you,
                Let me burn in hell forever.</p>
            <p>In a dilapidated three-room hut
                I've grown old and tired;
                This winter cold is the
                Worst I've ever suffered through.
                I sip thin gruel, waiting for the
                Freezing night to pass.
                Can I last until spring finally arrives?
                Unable to beg for rice,
                How will I survive the chill?
                Even meditation helps no longer;
                Nothing left to do but compose poems
                In memory of deceased friends.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect5><span class="font-7">Intentionally</span><span class="font-6">dramatic</span></h2>
        </div>
        <div class="content">
            <p>First blooming in the Western Paradise,
                The lotus has delighted us for ages.
                Its white petals are covered with dew,
                its jade green leaves spread out over the pond,
                And its pure fragrance perfumes the wind.
                Cool and majestic, it raises from the murky water.
                The sun sets behind the mountains
                But I remain in the darkness, too captivated to leave.</p>
        </div>
        <div class="content">
            <h2 class="content__title content__title--small" data-splitting data-effect6><span class="font-3">Blooming</span><span class="font-6">flowers</span></h2>
            <p>My house is buried in the deepest recess of the forest
            Every year, ivy vines grow longer than the year before.
            Undisturbed by the affairs of the world I live at ease,
            Woodmen’s singing rarely reaching me through the trees.
            While the sun stays in the sky, I mend my torn clothes
            And facing the moon, I read holy texts aloud to myself.
            Let me drop a word of advice for believers of my faith.
            To enjoy life's immensity, you do not need many things.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect7><span class="font-3">Unfolding</span><span class="font-4">Elegantly</span><span class="font-7">Naturally</span></h2>
        </div>
        <div class="content">
            <p>An old grave hidden away at the foot of a deserted hill,
            Overrun with rank weeds growing unchecked year after year;
            There is no one left to tend the tomb,
            And only an occasional woodcutter passes by.
            Once I was his pupil, a youth with shaggy hair,
            Learning deeply from him by the Narrow River.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect8><span class="font-8">Midnight</span></h2>
        </div>
        <div class="content">
            <p>Too lazy to be ambitious,
            I let the world take care of itself.
            Ten days' worth of rice in my bag;
            a bundle of twigs by the fireplace.
            Why chatter about delusion and enlightenment?
            Listening to the night rain on my roof,
            I sit comfortably, with both legs stretched out.</p>
        </div>
        <div class="content content--full">
            <h2 class="content__title" data-splitting data-effect9><span class="font-larger font-12">Moon</span><span class="font-upper font-sub font-1">Transportation</span></h2>
        </div>
        <div class="content">
            <p>Yes, I’m truly a dunce
            Living among trees and plants.
            Please don’t question me about illusion and enlightenment --
            This old fellow just likes to smile to himself.
            I wade across streams with bony legs,
            And carry a bag about in fine spring weather.
            That’s my life,
            And the world owes me nothing.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect10><span class="font-7">Lucid</span><span class="font-4">Dreaming</span><span class="font-medium font-1">Diaries</span></h2>
        </div>
        <div class="content">
            <p>In my youth I put aside my studies
            And I aspired to be a saint.
            Living austerely as a mendicant monk,
            I wandered here and there for many springs.
            Finally I returned home to settle under a craggy peak.
            I live peacefully in a grass hut,
            Listening to the birds for music.
            Clouds are my best neighbors.
            Below a pure spring where I refresh body and mind;
            Above, towering pines and oaks that provide shade and brushwood.
            Free, so free, day after day --
            I never want to leave!</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect11><span class="font-3 font-height">The Routine</span></h2>
        </div>
        <div class="content">
            <p>In stubborn stupidity, I live on alone
            befriended by trees and herbs.
            Too lazy to learn right from wrong,
            I laugh at myself, ignoring others.
            Lifting my bony shanks, I cross the stream,
            a sack in my hand, blessed by spring weather.
            Living thus, I want for nothing,
            at peace with all the world.</p>
            <p>Your finger points to the moon,
            but the finger is blind until the moon appears.
            What connection has  moon and finger?
            Are they separate objects or bound?
            This is a question for beginners
            wrapped in seas of ignorance.
            Yet one who looks beyond metaphor
            knows there is no finger; there is no moon.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect12><span class="font-9 font-height">Night Time</span></h2>
        </div>
        <div class="content">
            <p>Returning to my native village after many years’ absence:
            I put up at a country inn and listen to the rain.
            One robe, one bowl is all I have.
            I light incense and strain to sit in meditation;
            All night a steady drizzle outside the dark window &mdash;
            Inside, poignant memories of these long years of pilgrimage.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect13><span class="font-5">Megascrapers</span></h2>
        </div>
        <div class="content">
            <p>The city lights flicker and gleam,
            But true peace is found in stillness.
            A leaf falling, a drop of dew,
            Remind us to let go and find beauty in the moment.
            The rat race of desire,
            A never-ending cycle of pain,
            Why chase after illusions,
            When true happiness is found within.
            Like a fish swimming in the depths of a clear pond,
            We must dive deep within ourselves,
            To discover the pearl of true contentment.
            So let us cast aside our wants and needs,
            And find solace in simplicity.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect14><span class="font-11">Futuristic</span></h2>
        </div>
        <div class="content">
            <p>The universe is a vast and mysterious place, and yet everything is interconnected, everything is energy. Einstein's theory of relativity tells us that matter and energy are interchangeable, that everything is made up of tiny particles of energy vibrating at different frequencies. And this means that every living being, every rock, every star, every molecule is connected by an invisible web of energy.</p>
        </div>
        <div class="content">
            <h2 class="content__title" data-splitting data-effect15><span class="font-10">Unintelligible</span></h2>
        </div>
        <div class="content">
            <p>It is not easy to let go of our attachment to material possessions and desires. It takes discipline, focus and a certain level of detachment from the world around us. But by taking the time to slow down and appreciate the beauty of nature, and by quieting the mind and listening to the inner voice, we can find the path to true fulfillment.</p>
        </div>
        <div class="intro"><p><a class="line" href="index2.html">View more effects</a></p></div>
    </main>
    <script src="https://tympanus.net/codrops/adpacks/cda_sponsor.js"></script>
    <script type="module" src="js/index.js"></script>
</body>

</html>
함께 쓰는 파일 7개 보기
src/index2.html
파일 저장

<!DOCTYPE html>
<html lang="en" class="no-js">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>On-Scroll Typygorahy Animations | Set 2 | Codrops</title>
        <meta name="description" content="Some inspiration for text animations on scroll." />
        <meta name="keywords" content="typography, animation, on-scroll, gsap, ScrollTrigger" />
        <meta name="author" content="Codrops" />
        <link rel="shortcut icon" href="favicon.ico">
        <link rel="stylesheet" href="https://use.typekit.net/zda3gdw.css">
        <link rel="stylesheet" type="text/css" href="css/base.css" />
        <script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
        <script>
            document.documentElement.className = "js";
            var supportsCssVars = function() { var e, t = document.createElement("style"); return t.innerHTML = "root: { --tmp-var: bold; }", document.head.appendChild(t), e = !!(window.CSS && window.CSS.supports && window.CSS.supports("font-weight", "var(--tmp-var)")), t.parentNode.removeChild(t), e };
            supportsCssVars() || alert("Please view this demo in a modern browser that supports CSS Variables.");
        </script>
        <script src="//tympanus.net/codrops/adpacks/analytics.js"></script>
    </head>
    <body class="demo-2 loading">
        <main>
            <div class="frame">
                <div class="frame__title">
                    <a aria-label="Back to the article" class="frame__title-back" href="https://tympanus.net/codrops/?p=69734">
                        <span>Back to the article</span>
                        <svg width="18px" height="18px" viewBox="0 0 24 24">
                            <path vector-effect="non-scaling-stroke" d="M18.25 15.5a.75.75 0 00.75-.75v-9a.75.75 0 00-.75-.75h-9a.75.75 0 000 1.5h7.19L6.22 16.72a.75.75 0 101.06 1.06L17.5 7.56v7.19c0 .414.336.75.75.75z"></path>
                        </svg>
                    </a>
                </div>
                <a class="frame__prev" href="https://tympanus.net/Tutorials/LoopScrolling/">Previous demo</a>
                <nav class="frame__demos">
                    <a href="index.html">Set 1</a>
                    <span>Set 2</span>
                </nav>
            </div>
            <div class="intro">
                <h1 class="intro__title">
                    <span class="intro__title-pre">On-Scroll</span>
                    <span class="intro__title-sub">Typography Animations<sup><small>2</small></sup></span>
                </h1>
                <span class="intro__info">Please scroll moderately to fully experience the animations</span>
            </div>
            <div class="content">
                <h2 class="content__title content__title--left" data-splitting data-effect16>
                    <p class="font-small">As human beings, we often find ourselves feeling out of place in the world around us. We sense that something is not quite right, that the reality we experience is not necessarily the truth. And yet, we are bound by the beliefs and illusions that have been ingrained in us since childhood.
                        It can be a difficult and confusing journey to try and unravel these beliefs, to see beyond the layers of deception that cloud our vision. We may feel as though we need to make an effort, to strive for enlightenment, to grasp at something just beyond our reach.
                        But what if the answer is not found in effort, but in surrender? What if we simply allow ourselves to be carried by the flow of existence, to trust in the natural rhythm of life itself?
                    </p>
                </h2>
            </div>
            <div class="content">
                <h2 class="content__title" data-splitting data-effect17><span>Stubborn</span><span>Naivety</span></h2>
            </div>
            <div class="content">
                <p>It is in this surrender that we may find the truth of who we are, the essence of our being that lies just beyond the veil of illusion. We let go of our striving, our constant seeking, and instead allow ourselves to be swept up in the infinite mystery of existence.</p>
            </div>
            <div class="content">
                <h2 class="content__title" data-splitting data-effect18><span class="font-16 font-upper">Surrender to reality</span><span class="font-16 font-upper">Step into the light</span></h2>
            </div>
            <div class="content">
                <p>This surrender does not mean giving up, nor does it mean accepting the status quo. It is, in fact, quite the opposite. It is a willingness to let go of the illusions that have been holding us back, to see beyond the limitations of our own beliefs, and to embrace the fullness of life itself.</p>
            </div>
            <div class="content">
                <h2 class="content__title" data-splitting data-effect19><span class="font-21 font-upper">Intense</span><span class="font-21 font-upper">Nature</span></h2>
            </div>
            <div class="content">
                <p>It is a journey of discovery, of exploration, of coming to know ourselves in a deeper and more profound way. And in that journey, we may find the truth that has been eluding us, and the freedom that comes with it.</p>
            </div>
            <div class="content">
                <h2 class="content__title" data-splitting data-effect20><span class="font-22 font-upper">Waking</span><span class="font-12 font-upper">Life</span></h2>
            </div>
            <div class="content">
                <p>So let us surrender ourselves to the flow of existence, and trust in the journey that lies before us. For in that surrender, we may find the truth, and the beauty that comes with it.</p>
            </div>
            <div class="content content--full">
                <h2 class="content__title" data-splitting data-effect21><span class="font-20">Beauty</span><span class="font-18">remains</span></h2>
            </div>
            <div class="content">
                <p>The truth is that we are all simply waves in the ocean of existence, arising and falling back into the depths from which we came. And yet, we spend so much of our lives struggling against the natural ebb and flow of the universe, trying to hold on to what is fleeting and resist what is inevitable. </p>
            </div>
            <div class="content content--full">
                <h2 class="content__title" data-splitting data-effect22><span class="font-23 font-upper">Dance</span><span class="font-upper font-16">into</span><span class="font-upper font-23">Existence</span></h2>
            </div>
            <div class="content">
                <p> But the more we cling to the illusion of control, the more we suffer. The art of letting go is not a skill to be learned, but a state of being to be realized. It is the recognition that we are not separate from the world around us, but a part of it, and that our true power lies not in trying to dominate it, but in surrendering to it.</p>
            </div>
            <div class="content content--full">
                <h2 class="content__title" data-splitting data-effect23><span class="font-upper font-22">Deeper</span><span class=" font-upper font-20">Love</span><span class=" font-upper font-18">Light</span></h2>
            </div>
            <div class="content">
                <p>When we let go of our attachment to outcomes, our fear of the unknown, and our need for certainty, we open ourselves up to the infinite possibilities and potentialities of life.</p>
            </div>
            <div class="content content--full">
                <h2 class="content__title" data-splitting data-effect24><span class="font-15 font-upper">Embrace</span></h2>
            </div>
            <div class="content">
                <p>Letting go is not giving up, but rather a courageous act of trust in the universe and in ourselves. So let us embrace the art of letting go, and discover the freedom and joy that lie beyond our limited notions of control.</p>
            </div>
            <div class="content">
                <h2 class="content__title content__title--left" data-splitting data-effect25>
                   <span class="font-13 font-medium font-height-medium">
                        Feel because you have a heart<br>
                        And let it guide you through the dark.<br>
                        For it's the well of emotions deep,<br>
                        That helps you laugh, love, and sleep.<br>
                    </span>
                </h2>
            </div>
            <div class="content content--full">
                <h2 class="content__title" data-splitting data-effect26><span class="font-upper font-19">Cosmic</span><span class="font-upper font-23">Symphony</span></h2>
            </div>
            <div class="content">
                <p>Think because you have a mind,
                    that makes you unique and one of a kind.
                    With it you can learn and grow,
                    and see the world from its highest glow.
                </p>
            </div>
            <div class="content">
                <h2 class="content__title" data-splitting data-effect27>
                <span class="font-upper font-19 font-medium">
                Act because you have hands,
                that bring to life your every command.
                With them you can create and build,
                and bring to life what once was stilled.</span>
                </h2>
            </div>
            <div class="content content--full">
                <h2 class="content__title" data-splitting data-effect28><span class="font-12 font-medium">Liberation</span></h2>
            </div>
            <div class="content">
                <p>And when death comes, like the falling night,
                    remember the purpose that gave you flight.
                    For you were made to make a mark,
                    and leave behind a legacy bright and stark.
                </p>
            </div>
            <div class="content">
                <h2 class="content__title" data-splitting data-effect29><span class="font-upper">Discipline</span><span class="font-upper">above</span><span class="font-upper">Motivation</span><span class="font-upper">Always</span></h2>
            </div>
            <div class="content">
                <p>So feel with your heart, think with your mind,
                    and act with your hands, one of a kind.
                    For you are a gift, full of potential and might,
                    a spark of the divine, shining bright.
                </p>
            </div>
            <div class="intro"><p><a class="line" href="index.html">View the first set</a></p></div>
        </main>
        <script src="https://tympanus.net/codrops/adpacks/cda_sponsor.js"></script>
        <script type="module" src="js/index2.js"></script>
    </body>
</html>
src/js/index.js
파일 저장

import {preloadFonts} from './utils';
import Lenis from '@studio-freight/lenis'
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
import "splitting/dist/splitting.css";
import "splitting/dist/splitting-cells.css";
import Splitting from "splitting";

const wrapElements = (elems, wrapType, wrapClass) => {
    elems.forEach(char => {
        const wrapEl = document.createElement(wrapType);
        wrapEl.classList = wrapClass;
        char.parentNode.appendChild(wrapEl);
        wrapEl.appendChild(char);
    });
}

Splitting();

const fx1Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect1]')];
const fx2Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect2]')];
const fx3Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect3]')];
const fx4Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect4]')];
const fx5Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect5]')];
const fx6Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect6]')];
const fx7Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect7]')];
const fx8Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect8]')];
const fx9Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect9]')];
const fx10Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect10]')];
const fx11Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect11]')];
const fx12Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect12]')];
const fx13Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect13]')];
const fx14Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect14]')];
const fx15Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect15]')];

// Lenis smooth scrolling
let lenis;

// Initialize Lenis smooth scrolling
const initSmoothScrolling = () => {
	
    lenis = new Lenis({
		lerp: 0.2,
		smooth: true
	});

    lenis.on('scroll', () => ScrollTrigger.update());

	const scrollFn = (time) => {
		lenis.raf(time);
		requestAnimationFrame(scrollFn);
	};
	
    requestAnimationFrame(scrollFn);

};

// GSAP Scroll Triggers
const scroll = () => {
    
    fx1Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');

        gsap.fromTo(chars, { 
            'will-change': 'opacity, transform', 
            opacity: 0, 
            scale: 0.6,
            rotationZ: () => gsap.utils.random(-20,20)
        },
        {
            ease: 'power4',
            opacity: 1,
            scale: 1,
            rotation: 0,
            stagger: 0.4,
            scrollTrigger: {
                trigger: title,
                start: 'center+=20% bottom',
                end: '+=50%',
                scrub: true
            },
        });

    });
    
    fx2Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');

        gsap.fromTo(chars, { 
            'will-change': 'opacity, transform', 
            opacity: 0, 
            yPercent: 120, 
            scaleY: 2.3, 
            scaleX: 0.7, 
            transformOrigin: '50% 0%' 
        }, 
        {
            duration: 1,
            ease: 'back.inOut(2)',
            opacity: 1,
            yPercent: 0,
            scaleY: 1,
            scaleX: 1,
            stagger: 0.03,
            scrollTrigger: {
                trigger: title,
                start: 'center bottom+=50%',
                end: 'bottom top+=40%',
                scrub: true
            }
        });

    });

    fx3Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');

        gsap.fromTo(chars,  { 
            'will-change': 'transform', 
            transformOrigin: '50% 0%', 
            scaleY: 0
        },
        {
            ease: 'back',
            opacity: 1,
            scaleY: 1,
            yPercent: 0,
            stagger: 0.03,
            scrollTrigger: {
                trigger: title,
                start: 'center bottom-=5%',
                end: 'top top-=20%',
                scrub: true
            }
        });

    });

    fx4Titles.forEach(title => {
        
        const words = title.querySelectorAll('.word');
        
        for (const word of words) {
            
            const chars = word.querySelectorAll('.char');

            gsap.fromTo(chars,  { 
                'will-change': 'opacity, transform', 
                x: (position,_,arr) => 150*(position-arr.length/2) 
            },
            {
                ease: 'power1.inOut',
                x: 0,
                stagger: {
                    grid: 'auto',
                    from: 'center'
                },
                scrollTrigger: {
                    trigger: word,
                    start: 'center bottom+=30%',
                    end: 'top top+=15%',
                    scrub: true,
                }
            });

        };

    });

    fx5Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');

        gsap.fromTo(chars, { 
            'will-change': 'opacity, transform', 
            opacity: 0, 
            xPercent: () => gsap.utils.random(-200,200), 
            yPercent: () => gsap.utils.random(-150,150) 
        },
        {
            ease: 'power1.inOut',
            opacity: 1,
            xPercent: 0,
            yPercent: 0,
            stagger: { each: 0.05, grid: 'auto', from: 'random'},
            scrollTrigger: {
                trigger: title,
                start: 'center bottom+=10%',
                end: 'bottom center',
                scrub: 0.9
            }
        });

    });

    fx6Titles.forEach(title => {
        
        const words = title.querySelectorAll('.word');
        
        for (const word of words) {

            const chars = word.querySelectorAll('.char');

            chars.forEach(char => gsap.set(char.parentNode, { perspective: 2000 })); 

            gsap.fromTo(chars, { 
                'will-change': 'opacity, transform', 
                opacity: 0, 
                rotationX: -90,
                yPercent: 50
            },
            {
                ease: 'power1.inOut',
                opacity: 1,
                rotationX: 0,
                yPercent: 0,
                stagger: {
                    each: 0.03,
                    from: 0
                },
                scrollTrigger: {
                    trigger: word,
                    start: 'center bottom+=40%',
                    end: 'bottom center-=30%',
                    scrub: 0.9
                }
            });

        }

    });

    fx7Titles.forEach(title => {
        
        const words = title.querySelectorAll('.word');

        for (const word of words) {

            const chars = word.querySelectorAll('.char');

            chars.forEach(char => gsap.set(char.parentNode, { perspective: 2000 })); 

            gsap.fromTo(chars, { 
                'will-change': 'opacity, transform', 
                transformOrigin: '100% 50%',
                opacity: 0, 
                rotationY: -90,
                z: -300
            },
            {
                ease: 'expo',
                opacity: 1,
                rotationY: 0,
                z: 0,
                stagger: { each: 0.06, from: 'end'},
                scrollTrigger: {
                    trigger: word,
                    start: 'bottom bottom+=20%',
                    end: 'bottom top',
                    scrub: 1
                }
            });

        }

    });

    const lettersAndSymbols = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '!', '@', '#', '$', '%', '^', '&', '*', '-', '_', '+', '=', ';', ':', '<', '>', ','];
    fx8Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');

        chars.forEach((char, position) => {
            let initialHTML = char.innerHTML;
            
            gsap.fromTo(char, {
                opacity: 0
            },
            {
                duration: 0.03,
                innerHTML: () => lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)],
                repeat: 1,
                repeatRefresh: true,
                opacity: 1,
                repeatDelay: 0.03,
                delay: (position+1)*0.18,
                onComplete: () => gsap.set(char, {innerHTML: initialHTML, delay: 0.03}),
                scrollTrigger: {
                    trigger: title,
                    start: 'top bottom',
                    end: 'bottom center',
                    toggleActions: "play resume resume reset",
                    onEnter: () => gsap.set(char, {opacity: 0})
                }
            });

        });
        
    });

    fx9Titles.forEach(title => {

        const words = title.querySelectorAll('.word');

        for (const word of words) {

            const chars = word.querySelectorAll('.char');

            gsap.fromTo(chars,  { 
                'will-change': 'transform', 
                scaleX: 0,
                x: (_, target) => window.innerWidth/2 - target.offsetLeft - target.offsetWidth/2
            },
            {
                ease: 'power1.inOut',
                scaleX: 1,
                x: 0,
                scrollTrigger: {
                    trigger: word,
                    start: 'top bottom',
                    end: 'top top',
                    scrub: true
                }
            });

        }

    });

    fx10Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');

        gsap.fromTo(chars, { 
            'will-change': 'opacity', 
            opacity: 0,
            filter: 'blur(20px)'
        },
        {
            duration: 0.25,
            ease: 'power1.inOut',
            opacity: 1,
            filter: 'blur(0px)',
            stagger: { each: 0.05, from: 'random'},
            scrollTrigger: {
                trigger: title,
                start: 'top bottom',
                end: 'center center',
                toggleActions: "play resume resume reset"
            }
        });

    });

    fx11Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        wrapElements(chars, 'span', 'char-wrap');

        gsap.fromTo(chars, { 
            'will-change': 'transform', 
            transformOrigin: '0% 50%',
            xPercent: 105,
        }, 
        {
            duration: 1,
            ease: 'expo',
            xPercent: 0,
            stagger: 0.042,
            scrollTrigger: {
                trigger: title,
                start: 'top bottom',
                end: 'top top+=10%',
                toggleActions: "play resume resume reset",
            }
        });

    });

    fx12Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        wrapElements(chars, 'span', 'char-wrap');
    
        gsap.fromTo(chars, { 
            'will-change': 'transform', 
            xPercent: -250,
            rotationZ: 45,
            scaleX: 6,
            transformOrigin: '100% 50%'
        },
        {
            duration: 1,
            ease: 'power2',
            xPercent: 0,
            rotationZ: 0,
            scaleX: 1,
            stagger: -0.06,
            scrollTrigger: {
                trigger: title,
                start: 'top bottom+=10%',
                end: 'bottom top+=10%',
                scrub: true
            }
        });
    
    });

    fx13Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        
        chars.forEach(char => gsap.set(char.parentNode, { perspective: 2000 })); 

        gsap.fromTo(chars, { 
            'will-change': 'opacity, transform', 
            opacity: 0, 
            rotationY: 180,
            xPercent: -40,
            yPercent: 100
        },
        {
            ease: 'power4.inOut()',
            opacity: 1,
            rotationY: 0,
            xPercent: 0,
            yPercent: 0,
            stagger: {
                each: -0.03,
                from: 0
            },
            scrollTrigger: {
                trigger: title,
                start: 'center bottom',
                end: 'bottom center-=30%',
                scrub: 0.9
            }
        });

    });

    fx14Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        
        gsap.timeline()
        .fromTo(title, {
            'will-change': 'transform', 
            xPercent: 100
        }, {
            ease: 'none',
            xPercent: 0,
            scrollTrigger: {
                trigger: title,
                scrub: true,
                start: 'center center',
                end: '+=100%',
                pin: title.parentNode,
            }
        })
        .fromTo(chars, { 
            'will-change': 'transform', 
            scale: 3,
            yPercent: -900
        },
        {
            ease: 'back(2)',
            scale: 1,
            yPercent: 0,
            stagger: 0.05,
            scrollTrigger: {
                trigger: title,
                start: 'center center',
                end: '+=100%',
                scrub: 1.9,
            }
        }, 0);
        
    });

    fx15Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        
        chars.forEach(char => gsap.set(char.parentNode, { perspective: 2000 })); 
        
        gsap.timeline()
        .fromTo(title, {
            'will-change': 'transform', 
            xPercent: -80
        }, {
            ease: 'none',
            xPercent: 0,
            scrollTrigger: {
                trigger: title,
                scrub: true,
                start: 'center center',
                end: '+=100%',
                pin: title.parentNode,
            }
        })
        .fromTo(chars, { 
            'will-change': 'opacity, transform', 
            transformOrigin: '50% 50% -200px',
            rotationX: 380,
            opacity: 0,
        },
        {
            ease: 'expo.inOut',
            rotationX: 0,
            z: 0,
            opacity: 1,
            stagger: -0.03,
            scrollTrigger: {
                trigger: title,
                start: 'center center',
                end: '+=140%',
                scrub: 1.2,
            }
        }, 0);

    });

};

// Preload images and fonts
preloadFonts('cvn8slu').then(() => {
    // Remove loader (loading class)
    document.body.classList.remove('loading');
    // Lenis (smooth scrolling)
    initSmoothScrolling();
    // GSAP Scroll Triggers
    scroll();
});
src/js/index2.js
파일 저장

import {preloadFonts} from './utils';
import Lenis from '@studio-freight/lenis'
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
import "splitting/dist/splitting.css";
import "splitting/dist/splitting-cells.css";
import Splitting from "splitting";

Splitting();
const fx16Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect16]')];
const fx17Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect17]')];
const fx18Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect18]')];
const fx19Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect19]')];
const fx20Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect20]')];
const fx21Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect21]')];
const fx22Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect22]')];
const fx23Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect23]')];
const fx24Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect24]')];
const fx25Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect25]')];
const fx26Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect26]')];
const fx27Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect27]')];
const fx28Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect28]')];
const fx29Titles = [...document.querySelectorAll('.content__title[data-splitting][data-effect29]')];

// Lenis smooth scrolling
let lenis;

// Initialize Lenis smooth scrolling
const initSmoothScrolling = () => {
	
    lenis = new Lenis({
		lerp: 0.2,
		smooth: true
	});

    lenis.on('scroll', () => ScrollTrigger.update());

	const scrollFn = (time) => {
		lenis.raf(time);
		requestAnimationFrame(scrollFn);
	};
	
    requestAnimationFrame(scrollFn);

};

// GSAP Scroll Triggers
const scroll = () => {
    
    fx16Titles.forEach(title => {
        
        gsap.fromTo(title, {
            transformOrigin: '0% 50%',
            rotate: 3
        }, {
            ease: 'none',
            rotate: 0,
            scrollTrigger: {
                trigger: title,
                start: 'top bottom',
                end: 'top top',
                scrub: true,
            }
        });

        gsap.fromTo(title.querySelectorAll('.word'), {
            'will-change': 'opacity',
            opacity: 0.1
        }, 
        {
            ease: 'none',
            opacity: 1,
            stagger: 0.05,
            scrollTrigger: {
                trigger: title,
                start: 'top bottom-=20%',
                end: 'center top+=20%',
                scrub: true,
            }
        });

    });

    fx17Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        
        chars.forEach(char => gsap.set(char.parentNode, { perspective: 1000 })); 
        
        gsap.fromTo(chars, { 
            'will-change': 'opacity, transform', 
            opacity: 0,
            rotateX: () => gsap.utils.random(-120,120),
            z: () => gsap.utils.random(-200,200),
        }, 
        {
            ease: 'none',
            opacity: 1,
            rotateX: 0,
            z: 0,
            stagger: 0.02,
            scrollTrigger: {
                trigger: title,
                start: 'top bottom',
                end: 'bottom top',
                scrub: true,
            }
        });

    });

    fx18Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        
        chars.forEach(char => gsap.set(char.parentNode, { perspective: 1000 })); 
        
        gsap.fromTo(chars, { 
            'will-change': 'opacity, transform', 
            opacity: 0.2,
            z: -800
        }, 
        {
            ease: 'back.out(1.2)',
            opacity: 1,
            z: 0,
            stagger: 0.04,
            scrollTrigger: {
                trigger: title,
                start: 'top bottom',
                end: 'bottom top',
                scrub: true,
            }
        });

    });

    fx19Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        
        chars.forEach(char => gsap.set(char.parentNode, { perspective: 1000 })); 
        
        gsap.fromTo(chars, {
            'will-change': 'opacity, transform', 
            transformOrigin: '50% 0%',
            opacity: 0,
            rotationX: -90,
            z: -200
        }, 
        {
            ease: 'power1',
            opacity: 1,
            stagger: 0.05,
            rotationX: 0,
            z: 0,
            scrollTrigger: {
                trigger: title,
                start: 'center bottom',
                end: 'bottom top+=20%',
                scrub: true,
            }
        });
        
    });

    fx20Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        
        chars.forEach(char => gsap.set(char.parentNode, { perspective: 1000 })); 
        
        gsap.fromTo(chars, {
            'will-change': 'opacity, transform', 
            transformOrigin: '50% 100%',
            opacity: 0,
            rotationX: 90
        }, 
        {
            ease: 'power4',
            opacity: 1,
            stagger:  {
                each: 0.03,
                from: 'random'
            },
            rotationX: 0,
            scrollTrigger: {
                trigger: title,
                start: 'center bottom',
                end: 'bottom top+=20%',
                scrub: true,
            }
        });
        
    });

    fx21Titles.forEach(title => {
        
        const words = [...title.querySelectorAll('.word')];
        
        for (const word of words) {
            
            const chars = word.querySelectorAll('.char');
        
            chars.forEach(char => gsap.set(char.parentNode, { perspective: 2000 })); 

            gsap.fromTo(chars, {
                'will-change': 'opacity, transform', 
                opacity: 0,
                y: (position,_,arr) => -40*Math.abs(position-arr.length/2),
                z: () => gsap.utils.random(-1500,-600),
                rotationX: () => gsap.utils.random(-500,-200)
            }, 
            {
                ease: 'power1.inOut',
                opacity: 1,
                y: 0,
                z: 0,
                rotationX: 0,
                stagger: {
                    each: 0.06,
                    from: 'center'
                },
                scrollTrigger: {
                    trigger: word,
                    start: 'top bottom',
                    end: 'top top+=15%',
                    scrub: true,
                }
            });

        }

    });

    fx22Titles.forEach(title => {
        
        const words = [...title.querySelectorAll('.word')];
        
        for (const word of words) {
            const chars = word.querySelectorAll('.char');
            const charsTotal = chars.length;

            chars.forEach(char => gsap.set(char.parentNode, { perspective: 1000 })); 
            
            gsap.fromTo(chars, {
                'will-change': 'transform', 
                x: position => {
                    const factor = position < Math.ceil(charsTotal/2) ? position : Math.ceil(charsTotal/2) - Math.abs(Math.floor(charsTotal/2) - position) - 1;
                    return (charsTotal%2 ? Math.abs(Math.ceil(charsTotal/2)-1-factor) : Math.abs(Math.ceil(charsTotal/2)-factor) )*200*(position < charsTotal/2 ? -1 : 1);
                },
                y: position => {
                    const factor = position < Math.ceil(charsTotal/2) ? position : Math.ceil(charsTotal/2) - Math.abs(Math.floor(charsTotal/2) - position) - 1;
                    return factor*60;
                },
                rotationY: -270,
                rotationZ: position => {
                    const factor = position < Math.ceil(charsTotal/2) ? position : Math.ceil(charsTotal/2) - Math.abs(Math.floor(charsTotal/2) - position) - 1;
                    return position < charsTotal/2 ? Math.abs(factor-charsTotal/2)*8 : -1*Math.abs(factor-charsTotal/2)*8;
                }
            }, 
            {
                ease: 'power2.inOut',
                x: 0,
                y: 0,
                rotationZ: 0,
                rotationY: 0,
                scale: 1,
                scrollTrigger: {
                    trigger: word,
                    start: 'top bottom+=40%',
                    end: 'top top+=15%',
                    scrub: true,
                }
            });

        }

    });

    fx23Titles.forEach(title => {
        
        const words = [...title.querySelectorAll('.word')];
        
        for (const [wordPosition, word] of words.entries()) {

            gsap.fromTo(word.querySelectorAll('.char'), {
                'will-change': 'transform', 
                scale: 0.01,
                x: (pos,_,arr) => {
                    return wordPosition%2 ? pos*50 : (arr.length-pos-1)*-50
                }
            }, 
            {
                ease: 'power4',
                scale: 1,
                x: 0,
                scrollTrigger: {
                    trigger: word,
                    start: 'center bottom',
                    end: 'bottom top-=40%',
                    scrub: true,
                }
            });
            
        }
        
    });

    fx24Titles.forEach(title => {
        
        const chars = title.querySelectorAll('.char');
        const charsTotal = chars.length;
        
        gsap.fromTo(chars, {
            'will-change': 'transform', 
            y: position => {
                const factor = position < Math.ceil(charsTotal/2) ? position : Math.ceil(charsTotal/2) - Math.abs(Math.floor(charsTotal/2) - position) - 1;
                return (charsTotal/2-factor+6)*130;
            }
        }, 
        {
            ease: 'elastic.out(.4)',
            y: 0,
            stagger: {
                amount: 0.1,
                from: 'center'
            },
            scrollTrigger: {
                trigger: title,
                start: 'top bottom',
                end: 'bottom top-=50%',
                scrub: true,
            }
        });

    });

    fx25Titles.forEach(title => {
        
        gsap.fromTo(title.querySelectorAll('.char'), {
            'will-change': 'transform',
            transformOrigin: '50% 100%',
            scaleY: 0
        }, 
        {
            ease: 'power3.in',
            opacity: 1,
            scaleY: 1,
            stagger: 0.05,
            scrollTrigger: {
                trigger: title,
                start: 'center center',
                end: '+=500%',
                scrub: true,
                pin: title.parentNode,
            }
        });

    });

    fx26Titles.forEach(title => {
        
        const words = [...title.querySelectorAll('.word')];
        const tl = gsap.timeline({
            scrollTrigger: {
                trigger: title,
                start: 'center center',
                end: '+=100%',
                scrub: true,
                pin: title.parentNode,
            }
        });
        for (const [wordPosition, word] of words.entries()) {
            tl.fromTo(word.querySelectorAll('.char'), {
                'will-change': 'transform', 
                transformOrigin: () => !wordPosition%2 ? '50% 0%' : '50% 100%',
                scaleY: 0
            }, 
            {
                ease: 'power1.inOut',
                scaleY: 1,
                stagger: {
                    amount: 0.3,
                    from: 'center'
                }
            }, 0);
            
        }

    });

    fx27Titles.forEach(title => {
        
        const words = [...title.querySelectorAll('.word')];
        
        words.forEach(word => gsap.set(word.parentNode, { perspective: 1000 })); 

        gsap.fromTo(words, {
            'will-change': 'opacity, transform', 
            z: () => gsap.utils.random(500,950),
            opacity: 0,
            xPercent: (pos) => gsap.utils.random(-100,100),
            yPercent: (pos) => gsap.utils.random(-10,10),
            rotationX: () => gsap.utils.random(-90,90)
        }, 
        {
            ease: 'expo',
            opacity: 1,
            rotationX: 0,
            rotationY: 0,
            xPercent: 0,
            yPercent: 0,
            z: 0,
            scrollTrigger: {
                trigger: title,
                start: 'center center',
                end: '+=300%',
                scrub: true,
                pin: title.parentNode,
            },
            stagger: {
                each: 0.006,
                from: 'random'
            }
        });

    });

    fx28Titles.forEach(title => {
        
        const words = [...title.querySelectorAll('.word')];
        
        for (const word of words) {

            const chars = word.querySelectorAll('.char');
            const charsTotal = chars.length;
            
            gsap.fromTo(chars, {
                'will-change': 'transform, filter', 
                transformOrigin: '50% 100%',
                scale: position => {
                    const factor = position < Math.ceil(charsTotal/2) ? position : Math.ceil(charsTotal/2) - Math.abs(Math.floor(charsTotal/2) - position) - 1;
                    return gsap.utils.mapRange(0, Math.ceil(charsTotal/2), 0.5, 2.1, factor);
                },
                y: position => {
                    const factor = position < Math.ceil(charsTotal/2) ? position : Math.ceil(charsTotal/2) - Math.abs(Math.floor(charsTotal/2) - position) - 1;
                    return gsap.utils.mapRange(0, Math.ceil(charsTotal/2), 0, 60, factor);
                },
                rotation: position => {
                    const factor = position < Math.ceil(charsTotal/2) ? position : Math.ceil(charsTotal/2) - Math.abs(Math.floor(charsTotal/2) - position) - 1;
                    return position < charsTotal/2 ? gsap.utils.mapRange(0, Math.ceil(charsTotal/2), -4, 0, factor) : gsap.utils.mapRange(0, Math.ceil(charsTotal/2), 0, 4, factor);
                },
                filter: 'blur(12px) opacity(0)',
            }, 
            {
                ease: 'power2.inOut',
                y: 0,
                rotation: 0,
                scale: 1,
                filter: 'blur(0px) opacity(1)',
                scrollTrigger: {
                    trigger: word,
                    start: 'top bottom+=40%',
                    end: 'top top+=15%',
                    scrub: true,
                },
                stagger: {
                    amount: 0.15,
                    from: 'center'
                }
            });

        }

    });

    fx29Titles.forEach(title => {
        
        const words = [...title.querySelectorAll('.word')];
        
        for (const [pos,word] of words.entries()) {
            
            const chars = word.querySelectorAll('.char');
            
            gsap.fromTo(chars, {
                'will-change': 'transform', 
                transformOrigin: `${pos%2 ? 0 : 100}% ${pos%2 ? 100 : 0}%`,
                scale: 0
            }, 
            {
                ease: 'power4',
                scale: 1,
                stagger:  {
                    each: 0.03,
                    from: pos%2 ? 'end' : 'start'
                },
                scrollTrigger: {
                    trigger: word,
                    start: 'top bottom-=10%',
                    end: 'top top',
                    scrub: true,
                }
            });
        }
        
    });
};

// Preload images and fonts
preloadFonts('cvn8slu').then(() => {
    // Remove loader (loading class)
    document.body.classList.remove('loading');
    // Lenis (smooth scrolling)
    initSmoothScrolling();
    // GSAP Scroll Triggers
    scroll();
});
src/js/utils.js
파일 저장

// Preload images
const preloadFonts = (id) => {
    return new Promise((resolve) => {
        WebFont.load({
            typekit: {
                id: id
            },
            active: resolve
        });
    });
};

export {
    preloadFonts
};
Media credits and license evidence실행 안내·자료
파일 저장

README.md
## Credits

- Images from [Unsplash](https://unsplash.com/)


## License
[MIT](LICENSE)

Made with :blue_heart:  by [Codrops](http://www.codrops.com)
LICENSE실행 안내·자료
파일 저장

MIT License

Copyright (c) 2009 - 2022 [Codrops](https://tympanus.net/codrops)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Bundled dependency licenses실행 안내·자료
파일 저장

GSAP standard license snapshot
Source: https://gsap.com/community/standard-license/
Retrieved: 2026-09-22T04:22:49.089Z
Original distribution copyright and version headers remain in the runtime code.

Standard "No Charge" GSAP License

 I. DEFINITIONS

 “GSAP License” means the terms and conditions of this GSAP Software License Agreement.

 "GSAP Products" means any Software made available at gsap.com (https://gsap.com) or any successor sites, including but not limited to the GSAP animation library and related plugins, tools, or extensions.

 "Permitted Uses" means the implementation and/or use of GSAP Products on any website, web application, or digital interface by any person or entity (which may include, for clarity, those of companies that compete with Webflow in other areas of business).

 "Prohibited Uses" means any implementation and/or use of GSAP Products in tools that allow users to build visual animations without code that encourages, induces, or materially assists in creating a solution that competes with Webflow’s visual animation building capabilities.

 "Competitive Products" means any software, tool, or service that enables users to create, edit, or manage animations through a visual interface or builder similar to Webflow (https://webflow.com).

 II. GRANT OF LICENSE

 Subject to the terms and conditions of this GSAP License, Webflow grants you a non-exclusive, worldwide license to use, reproduce, display, and implement GSAP Products solely for Permitted Uses.

 III. RESTRICTIONS

 You may not:

 Use any GSAP Products for any Prohibited Uses without prior written consent;

 Reverse engineer any GSAP Products for the purpose of creating Competitive Products;

 Remove or alter any proprietary notices or branding from GSAP Products.

 IV. OWNERSHIP AND INTELLECTUAL PROPERTY

 All intellectual property rights in GSAP Products, including but not limited to copyright, patents, trademarks, and trade secrets, remain the exclusive property of Webflow. This GSAP License does not transfer any ownership rights in GSAP Products to you.

 V. TERMINATION

 Webflow may terminate this GSAP License and revoke your access in its discretion if you fail to comply with any of these terms and conditions. Upon termination, you must cease all use of GSAP Products and destroy all copies in your possession.

 VI. MISCELLANEOUS PROVISIONS

 General: This GSAP License is incorporated into and subject to Webflow’s Terms of Service available here (https://webflow.com/legal/terms) ("Terms of Service"). In the event of any conflict or inconsistency between this GSAP License and the Terms of Service, the terms of this GSAP License shall govern in relation to your use of any GSAP Products.

 Amendments: Webflow reserves the right to update or modify this GSAP License at any time by posting the revised terms on this website, provided that any such updates or modifications shall not result in any material degradation to the security, integrity, or functionality of any GSAP Products. You understand and agree that your continued use of any GSAP Products after such revisions to this GSAP License constitutes your acceptance of this GSAP License as revised. If you do not accept the revised GSAP License, you are prohibited from using versions of the GSAP Products released after the effective date of the revised GSAP License (as well as any updates made to previous versions). Notwithstanding, you may continue using previous versions of GSAP Products under the applicable terms licensed to you prior to the effective date of the revised GSAP License (for clarity, excluding any updates made thereto).

 No Waiver: Failure of Webflow to enforce any provision of this GSAP License shall not constitute a waiver of future enforcement of that or any other provision.

 FAQ

 Is it acceptable for AI tools like ChatGPT, Cursor, Lovable, Webstudio, etc. to generate GSAP code?
 Absolutely! AI-generated code is not a "Prohibited Use".

 What if a WordPress plugin or theme or other niche tool allows users to create GSAP-driven effects through a visual interface? Is that prohibited?
 We want to encourage developers to build on top of GSAP, including visual tools that don't directly compete with Webflow's rich animation-building capabilities. If you are not sure if your product might be considered a "Prohibited Use", feel free to contact us (https://gsap.com/contact) so we can talk through it!

 Can I really use GSAP in commercial projects without paying anything?
 Yes, really! Commercial usage is covered under the standard license. All of GSAP including the plugins that were formerly "members-only" like SplitText (https://gsap.com/docs/v3/Plugins/SplitText/) and MorphSVG (https://gsap.com/docs/v3/Plugins/MorphSVGPlugin) can be used in commercial projects at no charge. Enjoy! 💚

 Effective date: April 30, 2025

 Last modified date: May 30, 2025

 Copyright (©) 2025, Webflow


tiny-emitter@2.1.0 — LICENSE
The MIT License (MIT)

Copyright (c) 2017 Scott Corgan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



virtual-scroll@2.2.1 — LICENSE
The MIT License (MIT)

Copyright (c) 2014 Florian Morel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



splitting@1.1.0 — LICENSE.md
MIT License

Copyright (c) 2018 Stephen Shaw

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.