Lenis Scroll
<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.15/dist/lenis.css" />
<script src="https://unpkg.com/lenis@1.3.15/dist/lenis.min.js"></script>
<script>
const lenis = new Lenis({ duration: 1.4 });
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
</script>
Image Parallax
<script>
document.addEventListener('DOMContentLoaded', () => {
gsap.registerPlugin(ScrollTrigger);
const mm = gsap.matchMedia();
mm.add(
{
mobile: '(max-width: 479px)',
mobileLandscape: '(min-width: 480px) and (max-width: 767px)',
tablet: '(min-width: 768px) and (max-width: 991px)',
desktop: '(min-width: 992px)',
},
(context) => {
const { mobile, mobileLandscape, tablet, desktop } = context.conditions;
document.querySelectorAll('[data-parallax="trigger"]').forEach((trigger) => {
// Disable parallax on selected breakpoints
const disable = trigger.getAttribute('data-parallax-disable');
if ((disable === 'mobile' && mobile) || (disable === 'mobileLandscape' && mobileLandscape) || (disable === 'tablet' && tablet) || (disable === 'desktop' && desktop)) {
return;
}
// Target element
const target = trigger.querySelector('[data-parallax="target"]') || trigger;
// Direction
const direction = trigger.getAttribute('data-parallax-direction') || 'vertical';
const property = direction === 'horizontal' ? 'xPercent' : 'yPercent';
// Parallax values
const startValue = parseFloat(trigger.getAttribute('data-parallax-start')) || 0;
const endValue = parseFloat(trigger.getAttribute('data-parallax-end')) || -15;
// Scrub
const scrubAttribute = trigger.getAttribute('data-parallax-scrub');
const scrub = scrubAttribute !== null ? parseFloat(scrubAttribute) : true;
// ScrollTrigger positions
const scrollStart = trigger.getAttribute('data-parallax-scroll-start') || 'top bottom';
const scrollEnd = trigger.getAttribute('data-parallax-scroll-end') || 'bottom top';
gsap.fromTo(
target,
{
[property]: startValue,
},
{
[property]: endValue,
ease: 'none',
scrollTrigger: {
trigger: trigger,
start: `clamp(${scrollStart})`,
end: `clamp(${scrollEnd})`,
scrub: scrub,
invalidateOnRefresh: true,
},
},
);
});
ScrollTrigger.refresh();
},
);
});
</script>
Text Swapping
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-rotating-words]').forEach((el) => {
const words = el
.getAttribute('data-rotating-words')
.split(',')
.map((word) => word.trim())
.filter(Boolean);
if (words.length < 2) return;
let index = 0;
let current;
el.style.display = 'inline-grid';
el.style.overflow = 'hidden';
el.style.verticalAlign = 'bottom';
function createWord(word) {
const line = document.createElement('span');
line.style.gridArea = '1 / 1';
line.style.display = 'block';
line.style.whiteSpace = 'nowrap';
[...word].forEach((letter) => {
const char = document.createElement('span');
char.textContent = letter;
char.style.display = 'inline-block';
line.appendChild(char);
});
return line;
}
function showInitialWord() {
current = createWord(words[index]);
el.replaceChildren(current);
gsap.set(current.children, {
yPercent: 0,
autoAlpha: 1,
});
gsap.delayedCall(2, rotateWord);
}
function rotateWord() {
const nextIndex = (index + 1) % words.length;
const next = createWord(words[nextIndex]);
el.appendChild(next);
// Incoming letters start below, already visible
gsap.set(next.children, {
yPercent: 110,
autoAlpha: 1,
});
const tl = gsap.timeline({
onComplete: () => {
current.remove();
current = next;
index = nextIndex;
gsap.delayedCall(2, rotateWord);
},
});
// Old letters move up and new letters rise simultaneously
tl.to(
current.children,
{
yPercent: -110,
duration: 0.5,
stagger: 0.055,
ease: 'power2.in',
},
0,
);
tl.to(
next.children,
{
yPercent: 0,
duration: 0.5,
stagger: 0.055,
ease: 'power3.out',
},
0.4,
);
}
showInitialWord();
});
});
</script>
<style>
[data-rotating-words] {
display: inline-block;
overflow: hidden;
vertical-align: bottom;
}
.rotating-text__inner {
display: inline-grid;
overflow: hidden;
vertical-align: bottom;
}
.rotating-text__word {
grid-area: 1 / 1;
display: block;
white-space: nowrap;
transform: translateY(100%);
}
</style>