Lenis Script Documentation
This script integrates Lenis, a modern smooth-scrolling library, with GSAP (GreenSock Animation Platform) and ScrollTrigger. It provides an ultra-smooth, high-performance scrolling experience while ensuring all scroll-driven animations stay perfectly synchronized. By routing Lenis's requestAnimationFrame (raf) through GSAP's internal ticker and disabling lag smoothing, the setup prevents jitter and maintains precise scroll position tracking across all devices.
Key Features :
Smooth Scrolling: Delivers continuous, fluid scrolling using inertia (lerp: 0.1).
GSAP Synchronization: Keeps ScrollTrigger updates aligned with Lenis's custom scroll frame.
Lag Smoothing Override: Eliminates animation jumps during sudden frame drops or window re-focusing.
<!-- =========================================================
LENIS SMOOTH SCROLL
---------------------------------------------------------
Lenis handles smooth scrolling and is synchronized
with GSAP ScrollTrigger.
========================================================= -->
<script src="https://unpkg.com/lenis@1.3.4/dist/lenis.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.4/dist/lenis.css"/>
<script>
// Initialize Lenis smooth scrolling
const lenis = new Lenis({
smooth: true,
lerp: 0.1,
wheelMultiplier: 0.75,
infinite: false,
});
// Keep ScrollTrigger synchronized with Lenis
lenis.on("scroll", ScrollTrigger.update);
// Run Lenis through the GSAP ticker
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
// Disable GSAP lag smoothing for consistent scroll synchronization
gsap.ticker.lagSmoothing(0);
</script>
A. How to Edit Lenis Animations
You can adjust the smooth scroll performance and GSAP integration behavior directly in the script using these key parameters:
Lenis Smooth Scroll Settings:
const lenis = new Lenis({
smooth: true, // Enables/disables smooth scrolling (true/false)
lerp: 0.1, // Scroll interpolation/smoothness (lower values = smoother/slower catch-up)
wheelMultiplier: 0.75, // Mouse wheel scroll speed multiplier
infinite: false, // Enables or disables infinite looping scroll
});
GSAP Ticker & Lag Smoothing:
// Disables GSAP's lag smoothing to ensure GSAP and Lenis tick on the exact same frame
gsap.ticker.lagSmoothing(0);
B. Removing GSAP Animations
If you want to modify or remove the GSAP synchronization while keeping Lenis smooth scroll, follow these steps:
To disable GSAP synchronization entirely while keeping basic Lenis smooth scrolling, remove or comment out the GSAP integration lines:
// Remove or comment out these lines:
// lenis.on("scroll", ScrollTrigger.update);
// gsap.ticker.add((time) => { lenis.raf(time * 1000); });
// gsap.ticker.lagSmoothing(0);
Replace the ticker update with standard requestAnimationFrame logic to keep Lenis running independently:
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
Philosophy Scroll Animation Documentation
This script creates an interactive philosophy section using GSAP and ScrollTrigger. It combines two scroll-driven effects: animated SVG lines that bend according to the scroll direction, and philosophy blocks that rotate with momentum as the user scrolls.
Key Features :
Directional Bend: SVG philosophy lines bend left or right depending on the user's scroll direction.
Automatic Line Reset: Lines smoothly return to their original straight position when scrolling stops.
Momentum Rotation: Philosophy blocks rotate according to the direction of scrolling.
Rotation Limit: Prevents blocks from rotating beyond the defined maximum range.
Friction Effect: Creates a smooth deceleration after scrolling stops.
GSAP Ticker: Continuously updates block rotation for fluid motion.
ScrollTrigger: Controls when the philosophy animation becomes active within the viewport.
<script>
gsap.registerPlugin(ScrollTrigger);
// =========================================================
// BEND LINE
// =========================================================
const paths = document.querySelectorAll('.philosophy-line');
const bendAmount = 35;
let lastDirection = 0;
let debounce;
const straightD = 'M20,0 Q20,132.75 20,265.5';
const rightD = `M20,0 Q${20 + bendAmount},132.75 20,265.5`;
const leftD = `M20,0 Q${20 - bendAmount},132.75 20,265.5`;
// =========================================================
// ROTATE BLOCK
// =========================================================
const blocks = document.querySelectorAll('.block-line-philosophy');
const rotationSpeed = 70;
const rotationRange = 1420;
// Semakin mendekati 1 = semakin lama berhenti
const friction = 0.96;
let rotationVelocity = 0;
let scrollDirection = 0;
// =========================================================
// INITIAL BLOCK SETUP
// =========================================================
blocks.forEach((el) => {
el._baseAngle = parseFloat(el.getAttribute('data-angle')) || 0;
el._currentAngle = el._baseAngle;
gsap.set(el, {
rotation: el._baseAngle,
transformOrigin: '50% 100%',
});
});
// =========================================================
// SCROLL TRIGGER
// =========================================================
if (paths.length || blocks.length) {
ScrollTrigger.create({
trigger: '.wrapper-philosophy',
start: 'top bottom',
end: 'bottom top',
// =====================================================
// ON UPDATE
// =====================================================
onUpdate: (self) => {
scrollDirection = self.direction;
rotationVelocity = scrollDirection * rotationSpeed;
// ===================================================
// BEND LINE
// ===================================================
if (paths.length && self.direction !== lastDirection) {
lastDirection = self.direction;
gsap.to(paths, {
duration: 0.5,
overwrite: 'auto',
attr: {
d: self.direction === 1 ? rightD : leftD,
},
ease: 'power2.out',
});
}
// ===================================================
// RESET TIMER
// ===================================================
clearTimeout(debounce);
debounce = setTimeout(() => {
resetLine();
scrollDirection = 0;
}, 120);
},
});
// =======================================================
// RESET LINE
// =======================================================
function resetLine() {
if (!paths.length) return;
gsap.to(paths, {
duration: 0.6,
overwrite: 'auto',
attr: {
d: straightD,
},
ease: 'power2.inOut',
});
lastDirection = 0;
}
// =======================================================
// ROTATION LOOP
// =======================================================
gsap.ticker.add(() => {
if (!blocks.length) return;
const dt = gsap.ticker.deltaRatio(60) / 60;
blocks.forEach((el) => {
// ===============================================
// CURRENT OFFSET
// ===============================================
let offset = el._currentAngle - el._baseAngle;
// ===============================================
// APPLY ROTATION
// ===============================================
offset += rotationVelocity * dt;
// ===============================================
// ROTATION LIMIT
// ===============================================
offset = gsap.utils.clamp(-rotationRange, rotationRange, offset);
// ===============================================
// SAVE ANGLE
// ===============================================
el._currentAngle = el._baseAngle + offset;
// ===============================================
// APPLY TRANSFORM
// ===============================================
gsap.set(el, {
rotation: el._currentAngle,
});
});
// ===================================================
// FRICTION
// ===================================================
rotationVelocity *= Math.pow(friction, dt * 60);
// ===================================================
// STOP WHEN VERY SMALL
// ===================================================
if (Math.abs(rotationVelocity) < 0.05) {
rotationVelocity = 0;
}
});
}
</script>
A. How to Edit Philosophy Animations
You can customize the visual behavior of the Philosophy section directly in the script using the parameters below.
Bend Line Settings
const bendAmount = 35; //Higher value → stronger/more dramatic bend
const straightD = 'M20,0 Q20,132.75 20,265.5';
const rightD = `M20,0 Q${20 + bendAmount},132.75 20,265.5`;
const leftD = `M20,0 Q${20 - bendAmount},132.75 20,265.5`;
Line Animation Duration
if (paths.length && self.direction !== lastDirection) {
lastDirection = self.direction;
gsap.to(paths, {
duration: 0.5, //Lower value → faster response
overwrite: 'auto',
attr: {
d: self.direction === 1 ? rightD : leftD,
},
ease: 'power2.out',
});
}
Line Reset Duration
function resetLine() {
if (!paths.length) return;
gsap.to(paths, {
duration: 0.6, //Lower value → faster response
overwrite: 'auto',
attr: {
d: straightD,
},
ease: 'power2.inOut',
});
lastDirection = 0;
}
Editing Block Rotation
The .block-line-philosophy elements rotate based on the user's scroll direction.
const rotationSpeed = 70; //Higher value → faster rotation
const rotationRange = 1420; //Increasing the value allows the blocks to rotate further.
const friction = 0.96; //The closer the value is to 1, the longer the movement continues.
Editing the Scroll Trigger
trigger: '.wrapper-philosophy', //Class of main trigger
//This makes the animation operate within a more restricted viewport area.
start: 'top bottom',
end: 'bottom top',
B. Removing the Bend Line Animation
If you want to remove the SVG line bending effect while keeping the block rotation, remove or comment out the following parts:
Remove the line selector
const paths = document.querySelectorAll('.philosophy-line');
Remove the bend settings
const bendAmount = 35;
const straightD = 'M20,0 Q20,132.75 20,265.5';
const rightD = `M20,0 Q${20 + bendAmount},132.75 20,265.5`;
const leftD = `M20,0 Q${20 - bendAmount},132.75 20,265.5`;
Remove the line animation inside onUpdate
if (paths.length && self.direction !== lastDirection) {
...
}
Remove the resetLine() function
function resetLine() {
...
}