
Locomotive Scroll
- 1.5k installs
- 629 repo stars
- Updated November 20, 2025
- freshtechbro/claudedesignskills
locomotive-scroll is an agent skill for comprehensive skill for locomotive scroll smooth scrolling library with parallax effects, viewport detection, and scroll-driven animations. use this skill when implementing.
About
The locomotive-scroll skill is designed for comprehensive skill for Locomotive Scroll smooth scrolling library with parallax effects, viewport detection, and scroll-driven animations. Use this skill when implementing. Locomotive Scroll Comprehensive guide for implementing smooth scrolling, parallax effects, and scroll-driven animations using Locomotive Scroll. HTML Structure Every Locomotive Scroll implementation requires specific data attributes: 2. Invoke when the user asks about locomotive scroll or related SKILL.md workflows.
- Smooth scrolling: Hardware-accelerated smooth scroll with customizable easing.
- Parallax effects: Element-level speed control for depth.
- Viewport detection: Track when elements enter/exit viewport.
- Scroll events: Monitor scroll progress for animation synchronization.
- Sticky elements: Pin elements within defined boundaries.
Locomotive Scroll by the numbers
- 1,485 all-time installs (skills.sh)
- +99 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #239 of 1,888 Design & UI/UX skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
locomotive-scroll capabilities & compatibility
- Capabilities
- smooth scrolling: hardware accelerated smooth sc · parallax effects: element level speed control fo · viewport detection: track when elements enter/ex · scroll events: monitor scroll progress for anima
- Use cases
- frontend
What locomotive-scroll says it does
Comprehensive skill for Locomotive Scroll smooth scrolling library with parallax effects, viewport detection, and scroll-driven animations. Use this skill when implementing smooth
Comprehensive skill for Locomotive Scroll smooth scrolling library with parallax effects, viewport detection, and scroll-driven animations. Use this skill when
npx skills add https://github.com/freshtechbro/claudedesignskills --skill locomotive-scrollAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.5k |
|---|---|
| repo stars | ★ 629 |
| Security audit | 2 / 3 scanners passed |
| Last updated | November 20, 2025 |
| Repository | freshtechbro/claudedesignskills ↗ |
How do I comprehensive skill for locomotive scroll smooth scrolling library with parallax effects, viewport detection, and scroll-driven animations. use this skill when implementing?
Comprehensive skill for Locomotive Scroll smooth scrolling library with parallax effects, viewport detection, and scroll-driven animations. Use this skill when implementing.
Who is it for?
Developers using locomotive scroll workflows documented in SKILL.md.
Skip if: Skip when the task falls outside locomotive-scroll scope or needs a different stack.
When should I use this skill?
User asks about locomotive scroll or related SKILL.md workflows.
What you get
Completed locomotive-scroll workflow with documented commands, files, and expected deliverables.
- scroll animation components
- parallax section markup
- performant motion CSS or JS
By the numbers
- 739 installs listed on skills.sh
Files
Locomotive Scroll
Comprehensive guide for implementing smooth scrolling, parallax effects, and scroll-driven animations using Locomotive Scroll.
Overview
Locomotive Scroll is a JavaScript library that provides:
- Smooth scrolling: Hardware-accelerated smooth scroll with customizable easing
- Parallax effects: Element-level speed control for depth
- Viewport detection: Track when elements enter/exit viewport
- Scroll events: Monitor scroll progress for animation synchronization
- Sticky elements: Pin elements within defined boundaries
- Horizontal scrolling: Support for horizontal scroll layouts
When to use Locomotive Scroll:
- Building immersive landing pages with parallax
- Creating smooth, Apple-style scroll experiences
- Implementing scroll-triggered animations
- Developing narrative/storytelling websites
- Adding depth and motion to long-form content
Trade-offs:
- Scroll-hijacking can impact accessibility (provide disable option)
- Performance overhead on low-end devices (detect and disable)
- Mobile touch scrolling feels different (test extensively)
- Fixed positioning requires workarounds
Installation
npm install locomotive-scroll// ES6
import LocomotiveScroll from 'locomotive-scroll';
import 'locomotive-scroll/dist/locomotive-scroll.css';
// Or via CDN
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/locomotive-scroll/dist/locomotive-scroll.min.css">
<script src="https://cdn.jsdelivr.net/npm/locomotive-scroll/dist/locomotive-scroll.min.js"></script>Core Concepts
1. HTML Structure
Every Locomotive Scroll implementation requires specific data attributes:
<!-- Scroll container (required) -->
<div data-scroll-container>
<!-- Scroll sections (optional, improves performance) -->
<div data-scroll-section>
<!-- Tracked elements -->
<h1 data-scroll>Basic detection</h1>
<!-- Parallax element -->
<div data-scroll data-scroll-speed="2">
Moves faster than scroll
</div>
<!-- Sticky element -->
<div data-scroll data-scroll-sticky>
Sticks within section
</div>
<!-- Element with ID for tracking -->
<div data-scroll data-scroll-id="hero">
Accessible via JavaScript
</div>
<!-- Call event trigger -->
<div data-scroll data-scroll-call="fadeIn">
Triggers custom event
</div>
</div>
</div>2. Initialization
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
lerp: 0.1, // Smoothness (0-1, lower = smoother)
multiplier: 1, // Speed multiplier
class: 'is-inview', // Class added to visible elements
repeat: false, // Repeat in-view detection
offset: [0, 0] // Global trigger offset [bottom, top]
});3. Data Attributes
| Attribute | Purpose | Example |
|---|---|---|
data-scroll | Enable detection | data-scroll |
data-scroll-speed | Parallax speed | data-scroll-speed="2" |
data-scroll-direction | Parallax axis | data-scroll-direction="horizontal" |
data-scroll-sticky | Sticky positioning | data-scroll-sticky |
data-scroll-target | Sticky boundary | data-scroll-target="#section" |
data-scroll-offset | Trigger offset | data-scroll-offset="20%" |
data-scroll-repeat | Repeat detection | data-scroll-repeat |
data-scroll-call | Event trigger | data-scroll-call="myFunction" |
data-scroll-id | Unique identifier | data-scroll-id="hero" |
data-scroll-class | Custom class | data-scroll-class="is-visible" |
Common Patterns
1. Basic Smooth Scrolling
import LocomotiveScroll from 'locomotive-scroll';
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true
});<div data-scroll-container>
<div data-scroll-section>
<h1>Smooth scrolling enabled</h1>
</div>
</div>2. Parallax Effects
<!-- Slow parallax -->
<div data-scroll data-scroll-speed="0.5">
Moves slower than scroll (background effect)
</div>
<!-- Fast parallax -->
<div data-scroll data-scroll-speed="3">
Moves faster than scroll (foreground effect)
</div>
<!-- Reverse parallax -->
<div data-scroll data-scroll-speed="-2">
Moves in opposite direction
</div>
<!-- Horizontal parallax -->
<div data-scroll data-scroll-speed="2" data-scroll-direction="horizontal">
Moves horizontally
</div>3. Viewport Detection and Callbacks
// Track scroll progress
scroll.on('scroll', (args) => {
console.log(args.scroll.y); // Current scroll position
console.log(args.speed); // Scroll speed
console.log(args.direction); // Scroll direction
// Access specific element progress
if (args.currentElements['hero']) {
const progress = args.currentElements['hero'].progress;
console.log(`Hero progress: ${progress}`); // 0 to 1
}
});
// Call events
scroll.on('call', (value, way, obj) => {
console.log(`Event triggered: ${value}`);
// value = data-scroll-call attribute value
// way = 'enter' or 'exit'
// obj = {id, el}
});<div data-scroll data-scroll-id="hero">Hero section</div>
<div data-scroll data-scroll-call="playVideo">Video section</div>4. Sticky Elements
<!-- Stick within parent section -->
<div data-scroll-section>
<div data-scroll data-scroll-sticky>
I stick while section is in view
</div>
</div>
<!-- Stick with specific target -->
<div id="sticky-container">
<div data-scroll data-scroll-sticky data-scroll-target="#sticky-container">
I stick within #sticky-container
</div>
</div>5. Programmatic Scrolling
// Scroll to element
scroll.scrollTo('#target-section');
// Scroll to top
scroll.scrollTo('top');
// Scroll to bottom
scroll.scrollTo('bottom');
// Scroll with options
scroll.scrollTo('#target', {
offset: -100, // Offset in pixels
duration: 1000, // Duration in ms
easing: [0.25, 0.0, 0.35, 1.0], // Cubic bezier
disableLerp: true, // Disable smooth lerp
callback: () => console.log('Scrolled!')
});
// Scroll to pixel value
scroll.scrollTo(500);6. Horizontal Scrolling
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
direction: 'horizontal'
});<div data-scroll-container>
<div data-scroll-section style="display: flex; width: 300vw;">
<div>Section 1</div>
<div>Section 2</div>
<div>Section 3</div>
</div>
</div>7. Mobile Responsiveness
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
// Tablet settings
tablet: {
smooth: true,
breakpoint: 1024
},
// Smartphone settings
smartphone: {
smooth: false, // Disable on mobile for performance
breakpoint: 768
}
});Integration with GSAP ScrollTrigger
Locomotive Scroll and GSAP ScrollTrigger work together for advanced animations:
import LocomotiveScroll from 'locomotive-scroll';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
const locoScroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true
});
// Sync Locomotive Scroll with ScrollTrigger
locoScroll.on('scroll', ScrollTrigger.update);
ScrollTrigger.scrollerProxy('[data-scroll-container]', {
scrollTop(value) {
return arguments.length
? locoScroll.scrollTo(value, 0, 0)
: locoScroll.scroll.instance.scroll.y;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
pinType: document.querySelector('[data-scroll-container]').style.transform
? 'transform'
: 'fixed'
});
// GSAP animation with ScrollTrigger
gsap.to('.fade-in', {
scrollTrigger: {
trigger: '.fade-in',
scroller: '[data-scroll-container]',
start: 'top bottom',
end: 'top center',
scrub: true
},
opacity: 1,
y: 0
});
// Update ScrollTrigger when Locomotive updates
ScrollTrigger.addEventListener('refresh', () => locoScroll.update());
ScrollTrigger.refresh();Instance Methods
const scroll = new LocomotiveScroll();
// Lifecycle
scroll.init(); // Reinitialize
scroll.update(); // Refresh element positions
scroll.destroy(); // Clean up
scroll.start(); // Resume scrolling
scroll.stop(); // Pause scrolling
// Navigation
scroll.scrollTo(target, options);
scroll.setScroll(x, y);
// Events
scroll.on('scroll', callback);
scroll.on('call', callback);
scroll.off('scroll', callback);Performance Optimization
1. Use `data-scroll-section` to segment long pages:
<div data-scroll-container>
<div data-scroll-section>Section 1</div>
<div data-scroll-section>Section 2</div>
<div data-scroll-section>Section 3</div>
</div>2. Limit parallax elements - Too many can impact performance
3. Disable on mobile if performance is poor:
smartphone: { smooth: false }4. Update on resize:
window.addEventListener('resize', () => {
scroll.update();
});5. Destroy when not needed:
scroll.destroy();Common Pitfalls
1. Fixed Positioning Issues
Problem: position: fixed elements break with smooth scroll
Solution: Use data-scroll-sticky instead or add fixed elements outside container:
<!-- Fixed nav outside container -->
<nav style="position: fixed;">Navigation</nav>
<div data-scroll-container>
<!-- Page content -->
</div>2. Images Not Lazy Loading
Problem: All images load at once
Solution: Integrate with lazy loading:
<img data-scroll data-src="image.jpg" class="lazy">scroll.on('call', (func) => {
if (func === 'lazyLoad') {
// Trigger lazy load
}
});3. Scroll Position Not Updating
Problem: Dynamic content doesn't update scroll positions
Solution: Call update() after DOM changes:
// After adding content
addDynamicContent();
scroll.update();4. Accessibility Concerns
Problem: Screen readers and keyboard navigation broken
Solution: Provide disable option:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const scroll = new LocomotiveScroll({
smooth: !prefersReducedMotion
});5. Memory Leaks
Problem: Scroll instance not cleaned up on route changes (SPAs)
Solution: Always destroy on unmount:
// React example
useEffect(() => {
const scroll = new LocomotiveScroll();
return () => scroll.destroy();
}, []);6. Z-Index Fighting
Problem: Parallax elements overlap incorrectly
Solution: Set explicit z-index on parallax layers:
[data-scroll-speed] {
position: relative;
z-index: var(--layer-depth);
}Related Skills
- gsap-scrolltrigger: Advanced scroll-driven animations (use together)
- barba-js: Page transitions with Locomotive Scroll integration
- scroll-reveal-libraries: Simpler alternative for basic fade-in effects
- react-three-fiber: Scroll-driven 3D scenes (sync with Locomotive events)
- motion-framer: Alternative scroll animations in React
Resources
- Scripts:
generate_config.py- Configuration generator,integration_helper.py- GSAP integration code - References:
api_reference.md- Complete API,gsap_integration.md- GSAP ScrollTrigger patterns - Assets:
starter_locomotive/- Complete starter template with examples
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Locomotive Scroll Starter</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/locomotive-scroll@4/dist/locomotive-scroll.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Fixed Navigation (outside scroll container) -->
<nav class="nav">
<div class="nav-container">
<a href="#" class="logo">Locomotive</a>
<div class="nav-links">
<a href="#hero">Home</a>
<a href="#parallax">Parallax</a>
<a href="#sticky">Sticky</a>
<a href="#horizontal">Horizontal</a>
</div>
</div>
</nav>
<!-- Scroll Container -->
<div data-scroll-container>
<!-- Hero Section -->
<section data-scroll-section class="hero" id="hero">
<h1 data-scroll data-scroll-speed="2" data-scroll-id="hero-title">
Locomotive Scroll
</h1>
<p data-scroll data-scroll-speed="1" data-scroll-delay="0.1">
Premium smooth scrolling experience
</p>
<div data-scroll data-scroll-speed="0.5" class="hero-bg"></div>
</section>
<!-- Parallax Section -->
<section data-scroll-section class="parallax-section" id="parallax">
<h2 data-scroll data-scroll-speed="1">Parallax Effects</h2>
<div class="parallax-container">
<!-- Background layer (slow) -->
<div
data-scroll
data-scroll-speed="0.5"
class="parallax-layer layer-bg"
>
Background Layer
</div>
<!-- Middle layer (normal) -->
<div
data-scroll
data-scroll-speed="1"
class="parallax-layer layer-mid"
>
Middle Layer
</div>
<!-- Foreground layer (fast) -->
<div
data-scroll
data-scroll-speed="3"
class="parallax-layer layer-fg"
>
Foreground Layer
</div>
<!-- Reverse parallax -->
<div
data-scroll
data-scroll-speed="-2"
class="parallax-layer layer-reverse"
>
Reverse Layer
</div>
</div>
</section>
<!-- Sticky Section -->
<section data-scroll-section class="sticky-section" id="sticky">
<h2 data-scroll>Sticky Elements</h2>
<div class="sticky-container">
<div
data-scroll
data-scroll-sticky
data-scroll-target=".sticky-container"
class="sticky-element"
>
<h3>I'm Sticky!</h3>
<p>I stick while the section scrolls</p>
</div>
<div class="sticky-content">
<p data-scroll>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
<p data-scroll>Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p data-scroll>Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
<p data-scroll>Duis aute irure dolor in reprehenderit in voluptate velit esse.</p>
</div>
</div>
</section>
<!-- Horizontal Scroll Section (requires JavaScript setup) -->
<section data-scroll-section class="horizontal-section" id="horizontal">
<h2 data-scroll>Scroll-Triggered Elements</h2>
<div class="grid">
<div
data-scroll
data-scroll-call="fadeIn"
data-scroll-repeat
class="card"
>
<h3>Card 1</h3>
<p>Triggers callback on scroll</p>
</div>
<div
data-scroll
data-scroll-offset="20%"
class="card"
>
<h3>Card 2</h3>
<p>Custom trigger offset</p>
</div>
<div
data-scroll
data-scroll-class="is-visible"
class="card"
>
<h3>Card 3</h3>
<p>Custom in-view class</p>
</div>
<div
data-scroll
data-scroll-id="special-card"
class="card"
>
<h3>Card 4</h3>
<p>Tracked by JavaScript</p>
</div>
</div>
</section>
<!-- Footer -->
<footer data-scroll-section class="footer">
<p data-scroll>© 2025 Locomotive Scroll Starter</p>
<p data-scroll data-scroll-speed="2">Built with Locomotive Scroll</p>
</footer>
</div>
<script type="module" src="main.js"></script>
</body>
</html>
/**
* Locomotive Scroll Starter - Main JavaScript
*
* This file initializes Locomotive Scroll and demonstrates:
* - Basic smooth scrolling
* - Scroll event handling
* - Call events
* - Progress tracking
* - Programmatic scrolling
*/
import LocomotiveScroll from 'https://cdn.skypack.dev/locomotive-scroll@4';
// Initialize Locomotive Scroll
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
lerp: 0.1, // Smoothness (0-1, lower = smoother)
multiplier: 1, // Speed multiplier
class: 'is-inview', // Class added to visible elements
repeat: true, // Repeat in-view detection
offset: ['10%', 0], // Global offset [bottom, top]
getSpeed: true, // Track scroll speed
getDirection: true, // Track scroll direction
// Tablet settings
tablet: {
smooth: true,
breakpoint: 1024
},
// Smartphone settings
smartphone: {
smooth: true,
breakpoint: 768
}
});
// Update on window resize
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
scroll.update();
}, 250);
});
// Scroll event - Fires on every scroll update
scroll.on('scroll', (args) => {
// Current scroll position
const scrollY = args.scroll.y;
// Scroll speed and direction
const speed = args.speed;
const direction = args.direction; // 'up', 'down', 'left', 'right'
// Log scroll info (remove in production)
// console.log('Scroll Y:', scrollY, 'Speed:', speed, 'Direction:', direction);
// Track specific element progress
if (args.currentElements['hero-title']) {
const progress = args.currentElements['hero-title'].progress;
// Use progress (0 to 1) for custom animations
// console.log('Hero title progress:', progress);
}
// Track special card
if (args.currentElements['special-card']) {
const el = args.currentElements['special-card'];
// console.log('Special card in view:', el.inView);
}
});
// Call event - Fires when elements with data-scroll-call enter/exit viewport
scroll.on('call', (func, way, obj) => {
console.log(`Call event: ${func} - ${way}`);
// Handle specific callbacks
if (func === 'fadeIn') {
if (way === 'enter') {
console.log('Element entered viewport:', obj.el);
obj.el.style.opacity = '1';
} else if (way === 'exit') {
console.log('Element exited viewport:', obj.el);
}
}
});
// Smooth scroll to section on nav click
document.querySelectorAll('.nav-links a').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const target = link.getAttribute('href');
if (target === '#hero') {
scroll.scrollTo('top');
} else {
scroll.scrollTo(target, {
offset: -80, // Offset for fixed nav
duration: 1000, // Duration in ms
easing: [0.25, 0.0, 0.35, 1.0] // Cubic bezier
});
}
});
});
// Example: Programmatic scrolling
// Uncomment to test scrolling to specific elements
/*
setTimeout(() => {
// Scroll to parallax section after 3 seconds
scroll.scrollTo('#parallax', {
offset: -100,
duration: 2000,
callback: () => console.log('Scrolled to parallax section!')
});
}, 3000);
*/
// Example: Stop/Start scrolling
/*
// Stop scrolling
scroll.stop();
// Resume scrolling
setTimeout(() => {
scroll.start();
}, 2000);
*/
// Example: Set scroll position directly (no animation)
/*
scroll.setScroll(0, 500); // x, y
*/
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
scroll.destroy();
});
// Log initialization
console.log('Locomotive Scroll initialized');
console.log('Scroll instance:', scroll);
// Export for debugging
window.locomotiveScroll = scroll;
{
"name": "locomotive-scroll-starter",
"version": "1.0.0",
"description": "Locomotive Scroll starter template with examples",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"locomotive-scroll": "^4.1.4"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
Locomotive Scroll Starter Template
Complete starter template demonstrating Locomotive Scroll features including smooth scrolling, parallax effects, sticky elements, viewport detection, and scroll events.
Features
This template includes examples of:
- ✅ Smooth Scrolling - Hardware-accelerated smooth scroll with customizable lerp
- ✅ Parallax Effects - Multi-layered parallax with different speeds
- ✅ Sticky Elements - Elements that stick within defined boundaries
- ✅ Viewport Detection - Track when elements enter/exit viewport
- ✅ Scroll Events - Monitor scroll position, speed, and direction
- ✅ Call Events - Trigger callbacks when elements become visible
- ✅ Progress Tracking - Track element visibility progress (0 to 1)
- ✅ Programmatic Scrolling - Scroll to elements via JavaScript
- ✅ Mobile Responsive - Optimized settings for tablet and smartphone
Quick Start
Installation
npm installDevelopment Server
npm run devOpen http://localhost:5173 in your browser.
Build for Production
npm run buildFile Structure
starter_locomotive/
├── index.html # Main HTML with Locomotive Scroll markup
├── style.css # Styles including responsive design
├── main.js # Locomotive Scroll initialization and events
├── package.json # Dependencies
└── README.md # This fileUnderstanding the Code
HTML Structure
Every Locomotive Scroll project requires specific data attributes:
<!-- Main scroll container (required) -->
<div data-scroll-container>
<!-- Section wrapper (optional, improves performance) -->
<div data-scroll-section>
<!-- Tracked element -->
<h1 data-scroll data-scroll-speed="2">
Smooth Parallax
</h1>
</div>
</div>Key Data Attributes
| Attribute | Purpose | Example |
|---|---|---|
data-scroll | Enable detection | data-scroll |
data-scroll-speed | Parallax speed | data-scroll-speed="2" |
data-scroll-sticky | Sticky positioning | data-scroll-sticky |
data-scroll-call | Event trigger | data-scroll-call="fadeIn" |
data-scroll-id | Unique identifier | data-scroll-id="hero" |
Initialization Options
The template uses these configuration options:
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true, // Enable smooth scrolling
lerp: 0.1, // Smoothness (0-1, lower = smoother)
multiplier: 1, // Speed multiplier
class: 'is-inview', // Class added to visible elements
repeat: true, // Repeat in-view detection
offset: ['10%', 0], // Global offset [bottom, top]
getSpeed: true, // Track scroll speed
getDirection: true, // Track scroll direction
// Mobile settings
smartphone: {
smooth: true,
breakpoint: 768
}
});Examples in the Template
1. Parallax Effects
<!-- Slow parallax (background) -->
<div data-scroll data-scroll-speed="0.5">
Background Layer
</div>
<!-- Fast parallax (foreground) -->
<div data-scroll data-scroll-speed="3">
Foreground Layer
</div>
<!-- Reverse parallax -->
<div data-scroll data-scroll-speed="-2">
Reverse Layer
</div>2. Sticky Element
<div data-scroll-section>
<div data-scroll data-scroll-sticky>
I stick while section is in view
</div>
</div>3. Scroll Call Events
<div data-scroll data-scroll-call="fadeIn">
Triggers callback when visible
</div>scroll.on('call', (func, way) => {
if (func === 'fadeIn' && way === 'enter') {
// Element entered viewport
}
});4. Progress Tracking
<h1 data-scroll data-scroll-id="hero-title">
Hero Title
</h1>scroll.on('scroll', (args) => {
if (args.currentElements['hero-title']) {
const progress = args.currentElements['hero-title'].progress;
// progress ranges from 0 to 1
}
});5. Programmatic Scrolling
// Scroll to top
scroll.scrollTo('top');
// Scroll to element
scroll.scrollTo('#section');
// Scroll with options
scroll.scrollTo('#section', {
offset: -100,
duration: 1000,
callback: () => console.log('Done!')
});Performance Optimization
Use data-scroll-section
Segment long pages into sections for better performance:
<div data-scroll-container>
<div data-scroll-section>Section 1</div>
<div data-scroll-section>Section 2</div>
<div data-scroll-section>Section 3</div>
</div>Update on Resize
The template includes resize handling:
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => scroll.update(), 250);
});Cleanup
Always destroy the scroll instance:
window.addEventListener('beforeunload', () => {
scroll.destroy();
});Mobile Optimization
The template includes mobile-specific settings:
smartphone: {
smooth: true, // Enable/disable smooth scroll
breakpoint: 768 // Breakpoint in pixels
},
tablet: {
smooth: true,
breakpoint: 1024
}For best mobile performance, consider:
- Disabling smooth scroll on smartphones:
smooth: false - Reducing parallax speeds
- Limiting number of tracked elements
Accessibility
The template includes:
- Semantic HTML structure
- Proper heading hierarchy
- Color contrast compliance
- Keyboard navigation support
Consider adding:
prefers-reduced-motiondetection- Skip to content link
- ARIA labels where appropriate
Next Steps
1. Customize styles - Edit style.css to match your design 2. Add content - Replace placeholder content in index.html 3. Integrate GSAP - See GSAP integration examples in skill references 4. Add animations - Use scroll events to trigger custom animations 5. Optimize - Test on real devices and optimize for performance
Troubleshooting
Fixed elements not working?
Place fixed elements outside the scroll container:
<nav style="position: fixed;">Navigation</nav>
<div data-scroll-container>
<!-- Content -->
</div>Scroll position not updating?
Call update() after DOM changes:
addContent();
scroll.update();Accessibility concerns?
Provide option to disable smooth scroll:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const scroll = new LocomotiveScroll({
smooth: !prefersReducedMotion
});Resources
License
This starter template is free to use for any purpose.
/* Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
color: #333;
line-height: 1.6;
overflow-x: hidden;
}
/* Fixed Navigation */
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
color: #333;
}
.nav-links {
display: flex;
gap: 2rem;
}
.nav-links a {
text-decoration: none;
color: #666;
transition: color 0.3s;
}
.nav-links a:hover {
color: #333;
}
/* Scroll Container */
[data-scroll-container] {
padding-top: 60px; /* Account for fixed nav */
}
/* Sections */
section {
min-height: 100vh;
padding: 4rem 2rem;
}
h1, h2, h3 {
margin-bottom: 1rem;
}
/* Hero Section */
.hero {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
overflow: hidden;
}
.hero h1 {
font-size: clamp(3rem, 10vw, 6rem);
font-weight: bold;
margin-bottom: 1rem;
}
.hero p {
font-size: clamp(1.2rem, 3vw, 1.8rem);
opacity: 0.9;
}
.hero-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
pointer-events: none;
}
/* Parallax Section */
.parallax-section {
background: #f8f9fa;
position: relative;
}
.parallax-section h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
}
.parallax-container {
position: relative;
min-height: 80vh;
display: flex;
align-items: center;
justify-content: center;
}
.parallax-layer {
padding: 2rem 3rem;
border-radius: 12px;
font-size: 1.2rem;
font-weight: 600;
text-align: center;
margin: 1rem;
}
.layer-bg {
background: rgba(102, 126, 234, 0.2);
color: #667eea;
}
.layer-mid {
background: rgba(118, 75, 162, 0.2);
color: #764ba2;
}
.layer-fg {
background: rgba(237, 100, 166, 0.2);
color: #ed64a6;
}
.layer-reverse {
background: rgba(56, 178, 172, 0.2);
color: #38b2ac;
}
/* Sticky Section */
.sticky-section {
background: white;
}
.sticky-section h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
}
.sticky-container {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 2fr;
gap: 4rem;
align-items: start;
}
.sticky-element {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 3rem 2rem;
border-radius: 12px;
text-align: center;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
.sticky-element h3 {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.sticky-content p {
font-size: 1.2rem;
margin-bottom: 2rem;
padding: 1.5rem;
background: #f8f9fa;
border-radius: 8px;
opacity: 0;
transform: translateY(30px);
transition: opacity 0.6s, transform 0.6s;
}
.sticky-content p.is-inview {
opacity: 1;
transform: translateY(0);
}
/* Horizontal Section */
.horizontal-section {
background: #f8f9fa;
}
.horizontal-section h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.card {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
opacity: 0;
transform: translateY(50px);
transition: opacity 0.6s, transform 0.6s;
}
.card.is-inview,
.card.is-visible {
opacity: 1;
transform: translateY(0);
}
.card h3 {
color: #667eea;
margin-bottom: 0.5rem;
}
/* Footer */
.footer {
background: #333;
color: white;
text-align: center;
min-height: auto;
padding: 3rem 2rem;
}
.footer p {
margin-bottom: 0.5rem;
}
/* Responsive */
@media (max-width: 768px) {
.sticky-container {
grid-template-columns: 1fr;
}
.nav-links {
gap: 1rem;
font-size: 0.9rem;
}
.parallax-container {
flex-direction: column;
}
.grid {
grid-template-columns: 1fr;
}
}
/* Loading State */
html.has-scroll-smooth {
overflow: hidden;
}
html.has-scroll-dragging {
user-select: none;
}
/* Scrollbar Customization */
.c-scrollbar {
position: absolute;
right: 0;
top: 0;
width: 11px;
height: 100%;
transform-origin: center right;
transition: transform 0.3s, opacity 0.3s;
opacity: 0;
}
.c-scrollbar:hover {
transform: scaleX(1.45);
}
.c-scrollbar:hover,
.has-scroll-scrolling .c-scrollbar,
.has-scroll-dragging .c-scrollbar {
opacity: 1;
}
.c-scrollbar_thumb {
position: absolute;
top: 0;
right: 0;
background-color: #667eea;
opacity: 0.5;
width: 7px;
border-radius: 10px;
margin: 2px;
cursor: grab;
}
.has-scroll-dragging .c-scrollbar_thumb {
cursor: grabbing;
}
Locomotive Scroll API Reference
Complete API documentation for Locomotive Scroll.
Table of Contents
Constructor Options
new LocomotiveScroll(options)Global Options
| Option | Type | Default | Description |
|---|---|---|---|
el | HTMLElement | document | Scroll container element |
name | string | 'scroll' | Data attribute prefix |
offset | array | [0, 0] | Global offset [bottom, top] in px or % |
repeat | boolean | false | Repeat in-view detection |
smooth | boolean | false | Enable smooth scrolling |
initPosition | object | {x: 0, y: 0} | Initial scroll position |
direction | string | 'vertical' | Scroll direction: 'vertical' or 'horizontal' |
gestureDirection | string | 'vertical' | Gesture direction: 'vertical', 'horizontal', or 'both' |
reloadOnContextChange | boolean | false | Reload on window resize |
lerp | number | 0.1 | Linear interpolation amount (0-1, lower = smoother) |
class | string | 'is-inview' | Class applied to in-view elements |
scrollbarContainer | HTMLElement/boolean | false | Custom scrollbar container or false to hide |
scrollbarClass | string | 'c-scrollbar' | Custom scrollbar class |
scrollingClass | string | 'has-scroll-scrolling' | Class added while scrolling |
draggingClass | string | 'has-scroll-dragging' | Class added while dragging scrollbar |
smoothClass | string | 'has-scroll-smooth' | Class added when smooth enabled |
initClass | string | 'has-scroll-init' | Class added on init |
getSpeed | boolean | false | Add scroll speed to event |
getDirection | boolean | false | Add scroll direction to event |
scrollFromAnywhere | boolean | false | Trigger smooth scroll from anywhere |
multiplier | number | 1 | Scroll speed multiplier |
firefoxMultiplier | number | 50 | Firefox-specific multiplier |
touchMultiplier | number | 2 | Touch scroll multiplier |
resetNativeScroll | boolean | true | Reset scroll on refresh |
Mobile/Tablet Options
{
tablet: {
smooth: boolean,
direction: string,
gestureDirection: string,
breakpoint: number // Default: 1024
},
smartphone: {
smooth: boolean,
direction: string,
gestureDirection: string,
breakpoint: number // Default: 767
}
}Example:
new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
tablet: {
smooth: true,
breakpoint: 1024
},
smartphone: {
smooth: false,
breakpoint: 768
}
});Data Attributes
Container Attributes
| Attribute | Description |
|---|---|
data-scroll-container | Main scroll container (required) |
data-scroll-section | Section wrapper for performance optimization |
Example:
<div data-scroll-container>
<div data-scroll-section>
<!-- Content -->
</div>
</div>Element Attributes
| Attribute | Type | Description |
|---|---|---|
data-scroll | - | Mark element for detection |
data-scroll-id | string | Unique identifier for element |
data-scroll-class | string | Custom class when in view |
data-scroll-offset | string | Element-specific offset (px or %) |
data-scroll-repeat | boolean | Repeat in-view detection |
data-scroll-call | string | Function to call when in view |
data-scroll-speed | number | Parallax speed multiplier |
data-scroll-direction | string | Parallax direction: 'vertical' or 'horizontal' |
data-scroll-delay | number | Parallax delay (0-1) |
data-scroll-position | string | When to start parallax: 'top', 'bottom', 'left', 'right' |
data-scroll-target | string | Target element selector for position reference |
data-scroll-sticky | - | Enable sticky positioning |
Detailed Attribute Explanations
data-scroll-speed
Controls parallax intensity:
- Positive values: Element moves faster than scroll
- Negative values: Element moves in opposite direction
- 0-1: Element moves slower than scroll
- > 1: Element moves faster than scroll
<!-- Background layer (slow) -->
<div data-scroll data-scroll-speed="0.5">Slow</div>
<!-- Normal speed -->
<div data-scroll data-scroll-speed="1">Normal</div>
<!-- Foreground layer (fast) -->
<div data-scroll data-scroll-speed="3">Fast</div>
<!-- Reverse -->
<div data-scroll data-scroll-speed="-2">Reverse</div>data-scroll-direction
Axis for parallax effect:
<!-- Vertical parallax (default) -->
<div data-scroll data-scroll-speed="2" data-scroll-direction="vertical">V</div>
<!-- Horizontal parallax -->
<div data-scroll data-scroll-speed="2" data-scroll-direction="horizontal">H</div>data-scroll-offset
Custom trigger point:
<!-- Trigger when element is 20% from bottom -->
<div data-scroll data-scroll-offset="20%">Offset %</div>
<!-- Trigger 100px from bottom -->
<div data-scroll data-scroll-offset="100">Offset px</div>
<!-- Different bottom/top offsets -->
<div data-scroll data-scroll-offset="10%,30%">Custom offsets</div>data-scroll-sticky
Pin element within boundaries:
<!-- Sticky within section -->
<div data-scroll-section>
<div data-scroll data-scroll-sticky>
Sticks within section
</div>
</div>
<!-- Sticky with custom target -->
<div id="container">
<div data-scroll data-scroll-sticky data-scroll-target="#container">
Sticks within #container
</div>
</div>data-scroll-call
Trigger callback when in view:
<div data-scroll data-scroll-call="myFunction">
Triggers 'myFunction' when visible
</div>scroll.on('call', (func, way, obj) => {
if (func === 'myFunction') {
console.log('Element is', way); // 'enter' or 'exit'
}
});data-scroll-repeat
Re-trigger detection on each scroll:
<!-- Fires once (default) -->
<div data-scroll>Fires once</div>
<!-- Fires every time -->
<div data-scroll data-scroll-repeat>Fires repeatedly</div>Instance Methods
init()
Reinitialize scroll instance.
scroll.init();update()
Recalculate element positions. Call after DOM changes.
// After adding content
addContent();
scroll.update();destroy()
Remove event listeners and clean up. Essential for SPAs.
scroll.destroy();start()
Resume scrolling after stop().
scroll.start();stop()
Pause scrolling (disable smooth scroll).
scroll.stop();scrollTo(target, options)
Programmatically scroll to target.
Parameters:
target: Can be:'top'- Scroll to top'bottom'- Scroll to bottomnumber- Pixel valuestring- CSS selectorHTMLElement- DOM element
options(optional):
{
offset: number, // Offset in pixels (default: 0)
callback: function, // Callback after scroll
duration: number, // Duration in ms (default: 1000)
easing: [n,n,n,n], // Cubic bezier array (default: [0.25, 0.00, 0.35, 1.00])
disableLerp: boolean, // Disable smooth lerp (default: false)
onComplete: function // Same as callback
}Examples:
// Scroll to top
scroll.scrollTo('top');
// Scroll to element
scroll.scrollTo('#section');
// Scroll to pixel value
scroll.scrollTo(500);
// Scroll with options
scroll.scrollTo('#section', {
offset: -100,
duration: 2000,
easing: [0.25, 0.0, 0.35, 1.0],
callback: () => console.log('Done!')
});
// Instant scroll (no smooth)
scroll.scrollTo('#section', {
disableLerp: true
});setScroll(x, y)
Set scroll position instantly without animation.
scroll.setScroll(0, 500); // x, yon(event, callback)
Add event listener.
scroll.on('scroll', (args) => {
console.log(args);
});off(event, callback)
Remove event listener.
const handler = (args) => console.log(args);
scroll.on('scroll', handler);
scroll.off('scroll', handler);Events
scroll
Fires on scroll. Receives object with:
scroll.on('scroll', (args) => {
// Scroll position
console.log(args.scroll.x); // Horizontal scroll
console.log(args.scroll.y); // Vertical scroll
// Scroll limits
console.log(args.limit.x); // Max horizontal scroll
console.log(args.limit.y); // Max vertical scroll
// Speed (if getSpeed: true)
console.log(args.speed);
// Direction (if getDirection: true)
console.log(args.direction); // 'up', 'down', 'left', 'right'
// Current in-view elements
console.log(args.currentElements);
// Access specific element
if (args.currentElements['hero']) {
const el = args.currentElements['hero'];
console.log(el.progress); // 0 to 1
console.log(el.el); // DOM element
console.log(el.id); // data-scroll-id value
}
});currentElements structure:
{
'element-id': {
el: HTMLElement, // DOM element
id: string, // data-scroll-id
class: string, // data-scroll-class
top: number, // Distance from top
middle: number, // Distance from middle
bottom: number, // Distance from bottom
offset: object, // {top, bottom}
progress: number, // 0 to 1
inView: boolean, // Is in view
call: string // data-scroll-call value
}
}call
Fires when element with data-scroll-call enters/exits viewport.
scroll.on('call', (func, way, obj) => {
console.log(func); // data-scroll-call value
console.log(way); // 'enter' or 'exit'
console.log(obj); // {el, id}
});Example:
<div data-scroll data-scroll-call="playVideo">Video</div>scroll.on('call', (func, way) => {
if (func === 'playVideo' && way === 'enter') {
video.play();
}
});Properties
scroll.scroll
Current scroll state:
console.log(scroll.scroll);
// {
// x: 0, // Horizontal position
// y: 150 // Vertical position
// }scroll.limit
Max scroll values:
console.log(scroll.limit);
// {
// x: 0, // Max horizontal
// y: 2500 // Max vertical
// }scroll.speed
Current scroll speed (if getSpeed: true):
console.log(scroll.speed); // Numberscroll.direction
Current scroll direction (if getDirection: true):
console.log(scroll.direction); // 'up' | 'down' | 'left' | 'right'Complete Usage Example
import LocomotiveScroll from 'locomotive-scroll';
const scroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
lerp: 0.05,
multiplier: 1,
class: 'is-inview',
repeat: false,
offset: ['10%', 0],
getSpeed: true,
getDirection: true,
smartphone: {
smooth: false,
breakpoint: 768
}
});
// Track scroll
scroll.on('scroll', (args) => {
console.log(args.scroll.y);
if (args.currentElements['hero']) {
const progress = args.currentElements['hero'].progress;
// Sync with animation
}
});
// Handle call events
scroll.on('call', (func, way) => {
if (func === 'lazyLoad' && way === 'enter') {
loadImages();
}
});
// Update on window resize
window.addEventListener('resize', () => scroll.update());
// Cleanup on unmount
window.addEventListener('beforeunload', () => scroll.destroy());GSAP ScrollTrigger Integration with Locomotive Scroll
Complete guide for integrating Locomotive Scroll with GSAP ScrollTrigger for advanced scroll-driven animations.
Table of Contents
- Why Combine Them
- Basic Integration
- ScrollTrigger Proxy Setup
- Common Animation Patterns
- Performance Optimization
- Troubleshooting
Why Combine Them
Locomotive Scroll provides:
- Smooth scrolling UX
- Parallax effects
- Viewport detection
GSAP ScrollTrigger provides:
- Advanced timeline control
- Precise scrubbing
- Pin/snap functionality
- Complex animation sequencing
Together they create:
- Smooth scroll + scroll-driven animations
- Parallax + complex timelines
- Premium scroll experiences
Basic Integration
1. Installation
npm install locomotive-scroll gsap2. Complete Integration Setup
import LocomotiveScroll from 'locomotive-scroll';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
// Initialize Locomotive Scroll
const locoScroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
smartphone: {
smooth: true
},
tablet: {
smooth: true
}
});
// Sync Locomotive Scroll with ScrollTrigger
locoScroll.on('scroll', ScrollTrigger.update);
// Tell ScrollTrigger to use Locomotive Scroll's scroller
ScrollTrigger.scrollerProxy('[data-scroll-container]', {
scrollTop(value) {
return arguments.length
? locoScroll.scrollTo(value, {duration: 0, disableLerp: true})
: locoScroll.scroll.instance.scroll.y;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
pinType: document.querySelector('[data-scroll-container]').style.transform
? 'transform'
: 'fixed'
});
// Update ScrollTrigger when Locomotive Scroll updates
ScrollTrigger.addEventListener('refresh', () => locoScroll.update());
// Refresh both after DOM loads
ScrollTrigger.refresh();ScrollTrigger Proxy Setup
Understanding scrollerProxy
The scrollerProxy tells ScrollTrigger how to interact with Locomotive Scroll's custom scroller:
ScrollTrigger.scrollerProxy('[data-scroll-container]', {
// Get/Set scroll position
scrollTop(value) {
if (arguments.length) {
// Setter - scroll to position
locoScroll.scrollTo(value, {
duration: 0, // Instant
disableLerp: true // No smooth interpolation
});
} else {
// Getter - return current position
return locoScroll.scroll.instance.scroll.y;
}
},
// Define scroller dimensions
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
// pinType determines how pinning works
// 'transform' for smooth scroll, 'fixed' for native
pinType: document.querySelector('[data-scroll-container]').style.transform
? 'transform'
: 'fixed'
});For Horizontal Scroll
ScrollTrigger.scrollerProxy('[data-scroll-container]', {
scrollLeft(value) {
return arguments.length
? locoScroll.scrollTo(value, {duration: 0, disableLerp: true})
: locoScroll.scroll.instance.scroll.x;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
}
});Common Animation Patterns
1. Fade In on Scroll
gsap.to('.fade-in', {
scrollTrigger: {
trigger: '.fade-in',
scroller: '[data-scroll-container]',
start: 'top 80%',
end: 'top 50%',
scrub: true,
markers: true // Debug markers
},
opacity: 1,
y: 0,
duration: 1
});<div class="fade-in" style="opacity: 0; transform: translateY(50px);">
Fades in on scroll
</div>2. Pin Section While Scrolling
ScrollTrigger.create({
trigger: '#pinned-section',
scroller: '[data-scroll-container]',
pin: true,
start: 'top top',
end: 'bottom bottom',
pinSpacing: false
});<div data-scroll-section id="pinned-section">
This section pins while you scroll
</div>3. Scrubbed Timeline Animation
const tl = gsap.timeline({
scrollTrigger: {
trigger: '#animated-section',
scroller: '[data-scroll-container]',
start: 'top top',
end: 'bottom bottom',
scrub: 1, // Smooth scrubbing (1 second delay)
pin: true
}
});
tl.from('.box-1', { x: -100, opacity: 0 })
.from('.box-2', { x: 100, opacity: 0 })
.from('.box-3', { y: 100, opacity: 0 })
.to('.box-1', { rotation: 360, scale: 1.5 });4. Progress-Based Animation
Sync animations with Locomotive Scroll's progress values:
locoScroll.on('scroll', (args) => {
if (args.currentElements['hero']) {
const progress = args.currentElements['hero'].progress;
// Animate based on progress (0 to 1)
gsap.to('#hero-image', {
scale: 1 + progress * 0.5,
rotation: progress * 360,
duration: 0
});
}
});<div data-scroll data-scroll-id="hero">
<img id="hero-image" src="hero.jpg" alt="Hero">
</div>5. Horizontal Scroll Animation
const sections = gsap.utils.toArray('.panel');
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
ease: 'none',
scrollTrigger: {
trigger: '#horizontal-container',
scroller: '[data-scroll-container]',
pin: true,
scrub: 1,
end: () => `+=${document.querySelector('#horizontal-container').offsetWidth}`
}
});<div data-scroll-section id="horizontal-container">
<div class="panel">Panel 1</div>
<div class="panel">Panel 2</div>
<div class="panel">Panel 3</div>
</div>#horizontal-container {
display: flex;
width: 300vw;
}
.panel {
width: 100vw;
height: 100vh;
}6. Stagger Animation
gsap.from('.item', {
scrollTrigger: {
trigger: '.items-container',
scroller: '[data-scroll-container]',
start: 'top 80%',
end: 'top 30%',
scrub: 1
},
y: 100,
opacity: 0,
stagger: 0.2
});<div class="items-container" data-scroll-section>
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<div class="item">Item 4</div>
</div>7. Image Reveal Effect
gsap.to('.reveal-image img', {
scrollTrigger: {
trigger: '.reveal-image',
scroller: '[data-scroll-container]',
start: 'top 80%',
end: 'top 30%',
scrub: true
},
scale: 1,
clipPath: 'inset(0% 0% 0% 0%)'
});<div class="reveal-image">
<img src="image.jpg" style="scale: 1.2; clip-path: inset(10% 10% 10% 10%);">
</div>8. Text Split Animation
// Split text into characters
const text = document.querySelector('.split-text');
const chars = text.textContent.split('');
text.innerHTML = chars.map(char => `<span>${char}</span>`).join('');
// Animate each character
gsap.from('.split-text span', {
scrollTrigger: {
trigger: '.split-text',
scroller: '[data-scroll-container]',
start: 'top 80%',
end: 'top 50%',
scrub: 1
},
opacity: 0,
y: 50,
rotationX: -90,
stagger: 0.02
});Performance Optimization
1. Refresh Strategy
Only refresh when needed:
// Update Locomotive Scroll on window resize
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
locoScroll.update();
ScrollTrigger.refresh();
}, 250);
});2. Lazy Initialization
Create ScrollTriggers only when sections are near viewport:
locoScroll.on('call', (func, way, obj) => {
if (func === 'initAnimations' && way === 'enter') {
// Initialize animations for this section
initSectionAnimations(obj.el);
}
});<div data-scroll data-scroll-call="initAnimations">
<!-- Animations initialized only when visible -->
</div>3. Kill ScrollTriggers
Destroy when no longer needed:
const st = ScrollTrigger.create({
trigger: '.temp-animation',
scroller: '[data-scroll-container]',
onEnter: () => {
// Animation complete
st.kill(); // Remove ScrollTrigger
}
});4. Use will-change Sparingly
[data-scroll] {
/* Only on elements that actually animate */
will-change: transform;
}5. Normalize ScrollTrigger Updates
// Throttle updates
let ticking = false;
locoScroll.on('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
ScrollTrigger.update();
ticking = false;
});
ticking = true;
}
});Complete React Example
import { useEffect, useRef } from 'react';
import LocomotiveScroll from 'locomotive-scroll';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
function App() {
const scrollRef = useRef(null);
const locoScrollRef = useRef(null);
useEffect(() => {
const locoScroll = new LocomotiveScroll({
el: scrollRef.current,
smooth: true,
smartphone: { smooth: true },
tablet: { smooth: true }
});
locoScrollRef.current = locoScroll;
locoScroll.on('scroll', ScrollTrigger.update);
ScrollTrigger.scrollerProxy(scrollRef.current, {
scrollTop(value) {
return arguments.length
? locoScroll.scrollTo(value, {duration: 0, disableLerp: true})
: locoScroll.scroll.instance.scroll.y;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
pinType: scrollRef.current.style.transform ? 'transform' : 'fixed'
});
ScrollTrigger.addEventListener('refresh', () => locoScroll.update());
ScrollTrigger.refresh();
// Cleanup
return () => {
locoScroll.destroy();
ScrollTrigger.getAll().forEach(st => st.kill());
};
}, []);
useEffect(() => {
// Animation example
gsap.to('.animated-element', {
scrollTrigger: {
trigger: '.animated-element',
scroller: scrollRef.current,
start: 'top 80%',
scrub: true
},
opacity: 1,
y: 0
});
}, []);
return (
<div data-scroll-container ref={scrollRef}>
<div data-scroll-section>
<h1 className="animated-element">Hello World</h1>
</div>
</div>
);
}Troubleshooting
Issue: Animations Don't Trigger
Cause: ScrollTrigger not synced properly
Solution: Ensure scroller proxy is set correctly:
ScrollTrigger.scrollerProxy('[data-scroll-container]', {
// ... proxy setup
});Issue: Pin Not Working
Cause: pinType mismatch
Solution: Check transform vs fixed:
pinType: document.querySelector('[data-scroll-container]').style.transform
? 'transform'
: 'fixed'Issue: Scroll Position Jumps
Cause: Conflicting smooth scroll implementations
Solution: Disable lerp in scrollTo:
scrollTop(value) {
return arguments.length
? locoScroll.scrollTo(value, {duration: 0, disableLerp: true})
: locoScroll.scroll.instance.scroll.y;
}Issue: Markers Not Showing
Cause: Markers are positioned for native scroll
Solution: Use ScrollTrigger's built-in markers with scroller specified:
scrollTrigger: {
scroller: '[data-scroll-container]',
markers: true
}Issue: Performance Degradation
Solutions: 1. Reduce scrub value (use scrub: 1 instead of scrub: true) 2. Limit number of ScrollTriggers 3. Use once: true for one-time animations 4. Throttle scroll updates
Issue: React/SPA Route Changes
Cause: ScrollTriggers and Locomotive Scroll not cleaned up
Solution: Always destroy on unmount:
useEffect(() => {
const scroll = new LocomotiveScroll();
return () => {
scroll.destroy();
ScrollTrigger.getAll().forEach(st => st.kill());
};
}, []);Advanced Pattern: Locomotive + ScrollTrigger + Three.js
import * as THREE from 'three';
// Three.js scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight);
const renderer = new THREE.WebGLRenderer();
// Locomotive Scroll + ScrollTrigger setup
const locoScroll = new LocomotiveScroll({ /* ... */ });
// Sync 3D camera with scroll
locoScroll.on('scroll', (args) => {
const scrollY = args.scroll.y;
camera.position.y = scrollY * 0.001;
renderer.render(scene, camera);
});
// GSAP timeline for 3D objects
gsap.to(mesh.rotation, {
scrollTrigger: {
trigger: '#3d-section',
scroller: '[data-scroll-container]',
scrub: 1
},
y: Math.PI * 2
});Resources
#!/usr/bin/env python3
"""
Locomotive Scroll Configuration Generator
Generates Locomotive Scroll configuration code with customizable options.
Usage:
./generate_config.py # Interactive mode
./generate_config.py --smooth # Quick smooth scroll setup
./generate_config.py --horizontal # Horizontal scroll setup
./generate_config.py --preset performance # Use performance preset
"""
import sys
import json
PRESETS = {
"basic": {
"name": "Basic Smooth Scroll",
"options": {
"smooth": True,
"lerp": 0.1,
"multiplier": 1,
}
},
"smooth": {
"name": "Premium Smooth Experience",
"options": {
"smooth": True,
"lerp": 0.05,
"multiplier": 1,
"class": "is-inview",
"offset": ["10%", 0],
"getSpeed": True,
"getDirection": True,
}
},
"performance": {
"name": "Performance Optimized",
"options": {
"smooth": True,
"lerp": 0.1,
"multiplier": 1,
"smartphone": {
"smooth": False,
"breakpoint": 768
},
"tablet": {
"smooth": True,
"breakpoint": 1024
}
}
},
"horizontal": {
"name": "Horizontal Scroll",
"options": {
"smooth": True,
"direction": "horizontal",
"lerp": 0.1,
"multiplier": 1,
}
},
"parallax": {
"name": "Parallax & Detection",
"options": {
"smooth": True,
"lerp": 0.08,
"class": "is-inview",
"offset": ["10%", 0],
"repeat": False,
}
}
}
def print_header():
print("=" * 60)
print("Locomotive Scroll Configuration Generator")
print("=" * 60)
print()
def print_presets():
print("Available Presets:")
print()
for i, (key, preset) in enumerate(PRESETS.items(), 1):
print(f" {i}. {preset['name']} ({key})")
print()
def get_user_choice(prompt, options, default=None):
"""Get validated user input"""
while True:
if default:
choice = input(f"{prompt} (default: {default}): ").strip() or default
else:
choice = input(f"{prompt}: ").strip()
if choice in options:
return choice
print(f"Invalid choice. Please choose from: {', '.join(options)}")
def get_bool_input(prompt, default=True):
"""Get boolean input from user"""
default_str = "Y/n" if default else "y/N"
while True:
choice = input(f"{prompt} ({default_str}): ").strip().lower() or ("y" if default else "n")
if choice in ["y", "yes"]:
return True
elif choice in ["n", "no"]:
return False
print("Please enter 'y' or 'n'")
def get_number_input(prompt, default, min_val=None, max_val=None):
"""Get numeric input with validation"""
while True:
value = input(f"{prompt} (default: {default}): ").strip() or str(default)
try:
num = float(value)
if min_val is not None and num < min_val:
print(f"Value must be >= {min_val}")
continue
if max_val is not None and num > max_val:
print(f"Value must be <= {max_val}")
continue
return num
except ValueError:
print("Please enter a valid number")
def interactive_config():
"""Interactive configuration builder"""
print_header()
print("Let's build your Locomotive Scroll configuration!\n")
# Ask if user wants a preset
use_preset = get_bool_input("Start with a preset?", default=True)
if use_preset:
print_presets()
preset_choice = get_user_choice(
"Choose preset",
list(PRESETS.keys()),
default="basic"
)
config = PRESETS[preset_choice]["options"].copy()
print(f"\n✅ Starting with '{PRESETS[preset_choice]['name']}' preset\n")
else:
config = {}
# Core options
print("Core Options:")
print("-" * 40)
if "smooth" not in config:
config["smooth"] = get_bool_input("Enable smooth scrolling?", default=True)
if config.get("smooth"):
if "lerp" not in config:
config["lerp"] = get_number_input(
"Lerp value (smoothness, lower = smoother)",
default=0.1,
min_val=0.01,
max_val=1.0
)
if "direction" not in config:
direction = get_user_choice(
"Scroll direction",
["vertical", "horizontal"],
default="vertical"
)
if direction != "vertical":
config["direction"] = direction
if "multiplier" not in config:
config["multiplier"] = get_number_input(
"Speed multiplier",
default=1,
min_val=0.1,
max_val=5.0
)
# Advanced options
print("\nAdvanced Options:")
print("-" * 40)
if get_bool_input("Configure viewport detection?", default=False):
config["class"] = input("In-view class name (default: is-inview): ").strip() or "is-inview"
config["repeat"] = get_bool_input("Repeat in-view detection?", default=False)
if get_bool_input("Set global offset?", default=False):
bottom = input("Bottom offset (default: 0): ").strip() or "0"
top = input("Top offset (default: 0): ").strip() or "0"
config["offset"] = [bottom, top]
if get_bool_input("Enable scroll tracking (speed/direction)?", default=False):
config["getSpeed"] = True
config["getDirection"] = True
# Mobile options
print("\nMobile/Tablet Options:")
print("-" * 40)
if get_bool_input("Configure mobile settings?", default=True):
# Smartphone
config["smartphone"] = {
"smooth": get_bool_input("Enable smooth scroll on smartphones?", default=False),
"breakpoint": int(get_number_input("Smartphone breakpoint", default=768, min_val=320))
}
# Tablet
config["tablet"] = {
"smooth": get_bool_input("Enable smooth scroll on tablets?", default=True),
"breakpoint": int(get_number_input("Tablet breakpoint", default=1024, min_val=768))
}
return config
def generate_js_code(config):
"""Generate JavaScript initialization code"""
code = f"""// Locomotive Scroll Configuration
import LocomotiveScroll from 'locomotive-scroll';
import 'locomotive-scroll/dist/locomotive-scroll.css';
const scroll = new LocomotiveScroll({{
el: document.querySelector('[data-scroll-container]'),
{format_config_object(config, indent=1)}
}});
// Update on window resize
let resizeTimer;
window.addEventListener('resize', () => {{
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => scroll.update(), 250);
}});
// Cleanup
window.addEventListener('beforeunload', () => scroll.destroy());
export default scroll;
"""
return code
def format_config_object(config, indent=0):
"""Format configuration object for JavaScript"""
lines = []
indent_str = " " * indent
for key, value in config.items():
if isinstance(value, bool):
lines.append(f"{indent_str}{key}: {str(value).lower()},")
elif isinstance(value, (int, float)):
lines.append(f"{indent_str}{key}: {value},")
elif isinstance(value, str):
lines.append(f"{indent_str}{key}: '{value}',")
elif isinstance(value, list):
formatted_list = str(value).replace("'", '"')
lines.append(f"{indent_str}{key}: {formatted_list},")
elif isinstance(value, dict):
lines.append(f"{indent_str}{key}: {{")
lines.append(format_config_object(value, indent + 1))
lines.append(f"{indent_str}}},")
return "\n".join(lines)
def generate_html_template():
"""Generate HTML template"""
return """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Locomotive Scroll</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/locomotive-scroll/dist/locomotive-scroll.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
[data-scroll-container] {
/* Container styles */
}
[data-scroll-section] {
min-height: 100vh;
padding: 4rem 2rem;
}
.is-inview {
opacity: 1;
transform: translateY(0);
transition: opacity 0.6s, transform 0.6s;
}
[data-scroll] {
opacity: 0;
transform: translateY(50px);
}
</style>
</head>
<body>
<div data-scroll-container>
<div data-scroll-section>
<h1 data-scroll data-scroll-speed="2">
Locomotive Scroll
</h1>
<p data-scroll data-scroll-speed="1">
Smooth scrolling experience
</p>
</div>
<div data-scroll-section>
<h2 data-scroll>Section 2</h2>
<div data-scroll data-scroll-sticky>
Sticky element
</div>
</div>
<div data-scroll-section>
<h2 data-scroll>Section 3</h2>
<p data-scroll data-scroll-call="playVideo">
Triggers callback
</p>
</div>
</div>
<script type="module" src="main.js"></script>
</body>
</html>
"""
def main():
"""Main function"""
# Check for CLI arguments
if len(sys.argv) > 1:
arg = sys.argv[1].lstrip('-')
if arg in PRESETS:
config = PRESETS[arg]["options"].copy()
print(f"✅ Using '{PRESETS[arg]['name']}' preset")
elif arg == "help":
print(__doc__)
return
else:
print(f"Unknown option: {arg}")
print("Available presets:", ", ".join(PRESETS.keys()))
return
else:
# Interactive mode
config = interactive_config()
# Generate code
print("\n" + "=" * 60)
print("Generated Configuration")
print("=" * 60 + "\n")
js_code = generate_js_code(config)
print("JavaScript (main.js):")
print("-" * 60)
print(js_code)
# Ask if user wants HTML template
if get_bool_input("\nGenerate HTML template?", default=True):
html = generate_html_template()
print("\nHTML (index.html):")
print("-" * 60)
print(html)
# Save to files?
if get_bool_input("\nSave to files?", default=True):
with open("locomotive-config.js", "w") as f:
f.write(js_code)
print("✅ Saved to locomotive-config.js")
with open("locomotive-template.html", "w") as f:
f.write(generate_html_template())
print("✅ Saved to locomotive-template.html")
# Save config as JSON
with open("locomotive-config.json", "w") as f:
json.dump(config, f, indent=2)
print("✅ Saved to locomotive-config.json")
print("\n✅ Done!")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Locomotive Scroll + GSAP ScrollTrigger Integration Helper
Generates integration code for Locomotive Scroll with GSAP ScrollTrigger.
Usage:
./integration_helper.py # Interactive mode
./integration_helper.py --pattern fade-in # Generate specific pattern
./integration_helper.py --framework react # Framework-specific code
"""
import sys
ANIMATION_PATTERNS = {
"fade-in": {
"name": "Fade In on Scroll",
"description": "Elements fade in as they enter viewport",
"code": """gsap.to('.fade-in', {
scrollTrigger: {
trigger: '.fade-in',
scroller: '[data-scroll-container]',
start: 'top 80%',
end: 'top 50%',
scrub: true
},
opacity: 1,
y: 0,
duration: 1
});""",
"html": """<div class="fade-in" style="opacity: 0; transform: translateY(50px);">
Fades in on scroll
</div>"""
},
"pin-section": {
"name": "Pin Section",
"description": "Pin section while scrolling",
"code": """ScrollTrigger.create({
trigger: '#pinned-section',
scroller: '[data-scroll-container]',
pin: true,
start: 'top top',
end: 'bottom bottom',
pinSpacing: false
});""",
"html": """<div data-scroll-section id="pinned-section">
This section pins while you scroll
</div>"""
},
"timeline": {
"name": "Scrubbed Timeline",
"description": "Timeline scrubbed with scroll progress",
"code": """const tl = gsap.timeline({
scrollTrigger: {
trigger: '#animated-section',
scroller: '[data-scroll-container]',
start: 'top top',
end: 'bottom bottom',
scrub: 1,
pin: true
}
});
tl.from('.box-1', { x: -100, opacity: 0 })
.from('.box-2', { x: 100, opacity: 0 })
.from('.box-3', { y: 100, opacity: 0 })
.to('.box-1', { rotation: 360, scale: 1.5 });""",
"html": """<div data-scroll-section id="animated-section">
<div class="box-1">Box 1</div>
<div class="box-2">Box 2</div>
<div class="box-3">Box 3</div>
</div>"""
},
"horizontal-scroll": {
"name": "Horizontal Scroll",
"description": "Horizontal scrolling panels",
"code": """const sections = gsap.utils.toArray('.panel');
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
ease: 'none',
scrollTrigger: {
trigger: '#horizontal-container',
scroller: '[data-scroll-container]',
pin: true,
scrub: 1,
end: () => `+=\${document.querySelector('#horizontal-container').offsetWidth}`
}
});""",
"html": """<div data-scroll-section id="horizontal-container" style="display: flex; width: 300vw;">
<div class="panel" style="width: 100vw;">Panel 1</div>
<div class="panel" style="width: 100vw;">Panel 2</div>
<div class="panel" style="width: 100vw;">Panel 3</div>
</div>"""
},
"stagger": {
"name": "Stagger Animation",
"description": "Staggered element animations",
"code": """gsap.from('.item', {
scrollTrigger: {
trigger: '.items-container',
scroller: '[data-scroll-container]',
start: 'top 80%',
end: 'top 30%',
scrub: 1
},
y: 100,
opacity: 0,
stagger: 0.2
});""",
"html": """<div class="items-container" data-scroll-section>
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<div class="item">Item 4</div>
</div>"""
},
"progress-based": {
"name": "Progress-Based Animation",
"description": "Sync with Locomotive Scroll progress",
"code": """locoScroll.on('scroll', (args) => {
if (args.currentElements['hero']) {
const progress = args.currentElements['hero'].progress;
gsap.to('#hero-image', {
scale: 1 + progress * 0.5,
rotation: progress * 360,
duration: 0
});
}
});""",
"html": """<div data-scroll data-scroll-id="hero">
<img id="hero-image" src="hero.jpg" alt="Hero">
</div>"""
}
}
def print_header():
print("=" * 70)
print("Locomotive Scroll + GSAP ScrollTrigger Integration Helper")
print("=" * 70)
print()
def print_patterns():
print("Available Animation Patterns:")
print()
for i, (key, pattern) in enumerate(ANIMATION_PATTERNS.items(), 1):
print(f" {i}. {pattern['name']} ({key})")
print(f" {pattern['description']}")
print()
def get_user_choice(prompt, options, default=None):
"""Get validated user input"""
while True:
if default:
choice = input(f"{prompt} (default: {default}): ").strip() or default
else:
choice = input(f"{prompt}: ").strip()
if choice in options:
return choice
print(f"Invalid choice. Please choose from: {', '.join(options)}")
def get_bool_input(prompt, default=True):
"""Get boolean input from user"""
default_str = "Y/n" if default else "y/N"
while True:
choice = input(f"{prompt} ({default_str}): ").strip().lower() or ("y" if default else "n")
if choice in ["y", "yes"]:
return True
elif choice in ["n", "no"]:
return False
print("Please enter 'y' or 'n'")
def generate_base_integration():
"""Generate base Locomotive + GSAP integration code"""
return """// Locomotive Scroll + GSAP ScrollTrigger Integration
import LocomotiveScroll from 'locomotive-scroll';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
// Initialize Locomotive Scroll
const locoScroll = new LocomotiveScroll({
el: document.querySelector('[data-scroll-container]'),
smooth: true,
smartphone: {
smooth: true
},
tablet: {
smooth: true
}
});
// Sync Locomotive Scroll with ScrollTrigger
locoScroll.on('scroll', ScrollTrigger.update);
// Tell ScrollTrigger to use Locomotive Scroll's scroller
ScrollTrigger.scrollerProxy('[data-scroll-container]', {
scrollTop(value) {
return arguments.length
? locoScroll.scrollTo(value, {duration: 0, disableLerp: true})
: locoScroll.scroll.instance.scroll.y;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
pinType: document.querySelector('[data-scroll-container]').style.transform
? 'transform'
: 'fixed'
});
// Update ScrollTrigger when Locomotive Scroll updates
ScrollTrigger.addEventListener('refresh', () => locoScroll.update());
// Refresh both after DOM loads
ScrollTrigger.refresh();
"""
def generate_react_integration():
"""Generate React-specific integration"""
return """import { useEffect, useRef } from 'react';
import LocomotiveScroll from 'locomotive-scroll';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
function App() {
const scrollRef = useRef(null);
const locoScrollRef = useRef(null);
useEffect(() => {
const locoScroll = new LocomotiveScroll({
el: scrollRef.current,
smooth: true,
smartphone: { smooth: true },
tablet: { smooth: true }
});
locoScrollRef.current = locoScroll;
locoScroll.on('scroll', ScrollTrigger.update);
ScrollTrigger.scrollerProxy(scrollRef.current, {
scrollTop(value) {
return arguments.length
? locoScroll.scrollTo(value, {duration: 0, disableLerp: true})
: locoScroll.scroll.instance.scroll.y;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
pinType: scrollRef.current.style.transform ? 'transform' : 'fixed'
});
ScrollTrigger.addEventListener('refresh', () => locoScroll.update());
ScrollTrigger.refresh();
// Cleanup
return () => {
locoScroll.destroy();
ScrollTrigger.getAll().forEach(st => st.kill());
};
}, []);
// Add your animations in separate useEffect
useEffect(() => {
// Animation code here
}, []);
return (
<div data-scroll-container ref={scrollRef}>
<div data-scroll-section>
{/* Your content */}
</div>
</div>
);
}
export default App;
"""
def generate_vue_integration():
"""Generate Vue-specific integration"""
return """<template>
<div data-scroll-container ref="scrollContainer">
<div data-scroll-section>
<!-- Your content -->
</div>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue';
import LocomotiveScroll from 'locomotive-scroll';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
export default {
setup() {
const scrollContainer = ref(null);
let locoScroll = null;
onMounted(() => {
locoScroll = new LocomotiveScroll({
el: scrollContainer.value,
smooth: true
});
locoScroll.on('scroll', ScrollTrigger.update);
ScrollTrigger.scrollerProxy(scrollContainer.value, {
scrollTop(value) {
return arguments.length
? locoScroll.scrollTo(value, {duration: 0, disableLerp: true})
: locoScroll.scroll.instance.scroll.y;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight
};
},
pinType: scrollContainer.value.style.transform ? 'transform' : 'fixed'
});
ScrollTrigger.addEventListener('refresh', () => locoScroll.update());
ScrollTrigger.refresh();
});
onUnmounted(() => {
if (locoScroll) locoScroll.destroy();
ScrollTrigger.getAll().forEach(st => st.kill());
});
return { scrollContainer };
}
};
</script>
"""
def main():
"""Main function"""
# Check for CLI arguments
if len(sys.argv) > 1:
arg = sys.argv[1].lstrip('-')
if arg == "help":
print(__doc__)
return
elif arg == "pattern":
if len(sys.argv) > 2:
pattern_key = sys.argv[2]
if pattern_key in ANIMATION_PATTERNS:
pattern = ANIMATION_PATTERNS[pattern_key]
print(f"\n{pattern['name']}")
print("=" * 60)
print(f"{pattern['description']}\n")
print("JavaScript:")
print(pattern['code'])
print("\nHTML:")
print(pattern['html'])
else:
print(f"Unknown pattern: {pattern_key}")
print("Available patterns:", ", ".join(ANIMATION_PATTERNS.keys()))
else:
print("Please specify a pattern")
return
elif arg == "framework":
if len(sys.argv) > 2:
framework = sys.argv[2].lower()
if framework == "react":
print(generate_react_integration())
elif framework == "vue":
print(generate_vue_integration())
else:
print(f"Framework '{framework}' not supported. Available: react, vue")
else:
print("Please specify a framework (react or vue)")
return
# Interactive mode
print_header()
# Framework choice
print("Choose your framework:")
print(" 1. Vanilla JavaScript")
print(" 2. React")
print(" 3. Vue")
print()
framework = get_user_choice("Framework", ["1", "2", "3"], default="1")
print("\nGenerating base integration code...\n")
print("=" * 70)
if framework == "1":
base_code = generate_base_integration()
print(base_code)
elif framework == "2":
base_code = generate_react_integration()
print(base_code)
elif framework == "3":
base_code = generate_vue_integration()
print(base_code)
# Add animation patterns
if get_bool_input("\nAdd animation patterns?", default=True):
print_patterns()
patterns_to_add = []
while True:
pattern_key = get_user_choice(
"Choose pattern (or 'done' to finish)",
list(ANIMATION_PATTERNS.keys()) + ["done"],
default="done"
)
if pattern_key == "done":
break
patterns_to_add.append(pattern_key)
print(f"✅ Added {ANIMATION_PATTERNS[pattern_key]['name']}")
if patterns_to_add:
print("\n" + "=" * 70)
print("Animation Patterns")
print("=" * 70 + "\n")
for pattern_key in patterns_to_add:
pattern = ANIMATION_PATTERNS[pattern_key]
print(f"// {pattern['name']}")
print(f"// {pattern['description']}")
print(pattern['code'])
print()
print("\n" + "=" * 70)
print("Required HTML")
print("=" * 70 + "\n")
for pattern_key in patterns_to_add:
pattern = ANIMATION_PATTERNS[pattern_key]
print(f"<!-- {pattern['name']} -->")
print(pattern['html'])
print()
# Save to file?
if get_bool_input("\nSave to file?", default=True):
filename = "locomotive-gsap-integration.js"
with open(filename, "w") as f:
f.write(base_code)
if 'patterns_to_add' in locals() and patterns_to_add:
f.write("\n// Animation Patterns\n")
for pattern_key in patterns_to_add:
pattern = ANIMATION_PATTERNS[pattern_key]
f.write(f"\n// {pattern['name']}\n")
f.write(pattern['code'])
f.write("\n")
print(f"✅ Saved to {filename}")
print("\n✅ Done!")
if __name__ == "__main__":
main()
Related skills
How it compares
Pick locomotive-scroll when you need agent-generated scroll parallax code rather than static design mockups without implementation.
FAQ
What does locomotive-scroll do?
Comprehensive skill for Locomotive Scroll smooth scrolling library with parallax effects, viewport detection, and scroll-driven animations. Use this skill when implementing.
When should I use locomotive-scroll?
User asks about locomotive scroll or related SKILL.md workflows.
Is locomotive-scroll safe to install?
Review the Security Audits panel on this page before installing in production.