- 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:
< div data-scroll-container
< div data-scroll-section
< h1 data-scroll
Basic detection </ h1
< div data-scroll data-scroll-speed = " 2 "
Moves faster than scroll </ div
< div data-scroll data-scroll-sticky
Sticks within section </ div
< div data-scroll data-scroll-id = " hero "
Accessible via JavaScript </ div
< div data-scroll data-scroll-call = " fadeIn "
Triggers custom event </ div
</ div
</ div
- 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] } ) ;
- 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
- 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
- Parallax Effects
< div data-scroll data-scroll-speed = " 0.5 "
Moves slower than scroll (background effect) </ div
< div data-scroll data-scroll-speed = " 3 "
Moves faster than scroll (foreground effect) </ div
< div data-scroll data-scroll-speed = " -2 "
Moves in opposite direction </ div
< div data-scroll data-scroll-speed = " 2 " data-scroll-direction = " horizontal "
Moves horizontally </ div
- 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
- Sticky Elements
< div data-scroll-section
< div data-scroll data-scroll-sticky
I stick while section is in view </ div
</ div
< 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
- :
- 300
- vw
- ;
- "
- >
- <
- 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
- 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
- >
- Limit parallax elements
- - Too many can impact performance
- Disable on mobile
- if performance is poor:
- smartphone
- :
- {
- smooth
- :
- false
- }
- Update on resize
- :
- window
- .
- addEventListener
- (
- 'resize'
- ,
- (
- )
- =>
- {
- scroll
- .
- update
- (
- )
- ;
- }
- )
- ;
- 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:
< nav style = " position : fixed ; "
Navigation </ nav
< div data-scroll-container
- </
- 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 ) ; }