Building a React Portfolio Website with Locomotive Scroll

Create stunning scroll-based animations with smooth scrolling, parallax effects, and scroll-triggered reveals

Introduction

Smooth scrolling animations have become a defining characteristic of modern portfolio websites. When implemented thoughtfully, these animations create a fluid, cinematic browsing experience that engages visitors and showcases technical sophistication. Locomotive Scroll is a powerful JavaScript library that enables developers to create stunning scroll-based animations in React applications, transforming standard scrolling into an interactive storytelling experience.

For developers working with Next.js, Locomotive Scroll integrates seamlessly with the framework's component-based architecture. The library enables portfolio websites that not only look exceptional but also perform well, as our approach to React development emphasizes both visual impact and technical excellence. Whether you're building a personal portfolio to showcase your work or creating a client-facing showcase site, mastering scroll-based animations elevates your projects above ordinary implementations.

What is Locomotive Scroll?

Locomotive Scroll is a React scroll library built on ayamflow's virtual-scroll, a lightweight library for creating custom scrollers that support touch and keyboard navigation. The library provides comprehensive support for various forms of scrolling animations, including smooth scrolling, animated page overlays, parallax effects, sticky scroll, and scroll-triggered visibility classes from LogRocket's comprehensive tutorial.

Unlike basic CSS scroll animations, Locomotive Scroll offers granular control over scroll behavior, allowing developers to create complex, layered animations that respond dynamically to user interaction. The library intercepts native scroll behavior and replaces it with a custom scrolling engine that can animate elements based on scroll position, velocity, and direction.

The library has evolved through several versions, with Locomotive Scroll v4 representing the current standard for React implementations. This version introduces improved performance, better TypeScript support, and more predictable behavior across different browsers and devices.

Why Use Locomotive Scroll for Portfolios?

Portfolio websites serve a dual purpose: they showcase your work while demonstrating your technical capabilities. Locomotive Scroll helps achieve both objectives by enabling animations that would be difficult or impossible to achieve with CSS alone. The library excels at creating narrative-driven scrolling experiences where content reveals itself in engaging ways as users explore the page as covered by Pulse Studio Lab.

Common applications include parallax hero sections where background and foreground elements move at different speeds, sticky sections that hold attention while content scrolls past, and scroll-triggered animations that bring elements into view with smooth transitions. These techniques create a sense of polish and professionalism that distinguishes high-quality portfolios from basic implementations.

Installation and Basic Setup

Getting started with Locomotive Scroll in a React project requires installing the core package along with the React-specific wrapper. The installation process is straightforward, but proper configuration is essential for smooth operation as detailed in the Pulse Studio Lab guide.

npm Installation

npm install locomotive-scroll react-locomotive-scroll

After installation, the library requires specific CSS styles to function correctly. These styles handle the custom scroll container, smooth scrolling behavior, and custom scrollbar appearance. Without proper CSS configuration, the library may not behave as expected per LogRocket's documentation.

Required CSS Styles

Locomotive Scroll needs specific CSS rules to manage its custom scrolling behavior. The following styles handle the smooth scroll container, prevent native scroll behavior, and customize the appearance of the scrollbar:

html.has-scroll-smooth {
 overflow: hidden;
}

html.has-scroll-smooth body {
 overflow: hidden;
}

html.has-scroll-smooth [data-scroll-container] {
 min-height: 100vh;
}

.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: black;
 opacity: 0.5;
 width: 7px;
 border-radius: 10px;
 margin: 2px;
 cursor: -webkit-grab;
 cursor: grab;
}

.has-scroll-dragging .c-scrollbar_thumb {
 cursor: -webkit-grabbing;
 cursor: grabbing;
}

These styles create a minimal, unobtrusive scrollbar that only becomes visible when scrolling or hovering, maintaining a clean visual design while providing necessary feedback about scroll position.

Implementing Smooth Scrolling

Smooth scrolling is Locomotive Scroll's most recognizable feature. Rather than the abrupt jump of native browser scrolling, smooth scrolling creates a gradual transition between scroll positions, giving the page a fluid, premium feel. This effect works by intercepting scroll events and animating the scroll position over time using requestAnimationFrame as explained in LogRocket's implementation guide.

React Implementation Pattern

In React, Locomotive Scroll should be initialized within a useEffect hook to ensure the DOM is fully rendered before attaching the scroll behavior. The initialization requires a reference to a container element that will become the scroll wrapper:

import { useEffect } from 'react';
import LocomotiveScroll from 'locomotive-scroll';
import 'locomotive-scroll/dist/locomotive-scroll.css';

function Portfolio() {
 useEffect(() => {
 const scroll = new LocomotiveScroll({
 el: document.querySelector('[data-scroll-container]'),
 smooth: true,
 multiplier: 1,
 lerp: 0.1
 });

 return () => {
 if (scroll) scroll.destroy();
 };
 }, []);

 return (
 <main data-scroll-container>
 <section data-scroll-section>
 <h1 data-scroll data-scroll-speed="2">Welcome to My Portfolio</h1>
 </section>
 </main>
 );
}

The lerp parameter controls the interpolation factor for smooth scrolling, with lower values creating a more pronounced smoothing effect. A value of 0.1 provides noticeable smoothness without feeling sluggish, while values closer to 1 feel more responsive.

HTML Structure Requirements

Locomotive Scroll relies on specific data attributes to identify scrollable sections and animated elements. The [data-scroll-container] attribute marks the main scroll wrapper, while [data-scroll-section] identifies individual sections within the scrollable area per Pulse Studio Lab's documentation:

<main data-scroll-container>
 <section data-scroll-section>
 <h1 data-scroll data-scroll-speed="2">Fast Element</h1>
 <p data-scroll data-scroll-speed="0.5">Slow Element</p>
 </section>
</main>

This structure allows Locomotive Scroll to track scroll progress across different sections and apply appropriate animations based on scroll position.

Creating Parallax Effects

Parallax scrolling creates depth by moving different page elements at different speeds as the user scrolls. Locomotive Scroll implements parallax through the data-scroll-speed attribute, where values greater than 1 move elements faster than the scroll (appearing closer), and values less than 1 move elements slower (appearing further away) as documented by LogRocket.

Speed Control Fundamentals

The speed value determines how quickly an element moves relative to the scroll position:

  • Values greater than 1: Elements move faster than scroll, creating a foreground effect
  • Values between 0 and 1: Elements move slower than scroll, creating a background effect
  • Negative values: Elements move in the opposite direction of scroll

For portfolio websites, parallax works particularly well in hero sections where a background image moves slowly while text and call-to-action elements move more quickly, creating visual depth and interest. This technique is especially effective when combined with our frontend development expertise to create cohesive, performant animations.

Implementing Multi-Layer Parallax

Complex parallax effects require layering multiple elements with different speed values. This technique creates the illusion of depth as different layers move independently during scrolling:

<header data-scroll-section className="hero-section">
 <div
 data-scroll
 data-scroll-speed="-2"
 className="hero-background"
 >
 <img src="/hero-image.jpg" alt="Background" />
 </div>
 <div
 data-scroll
 data-scroll-speed="1"
 className="hero-content"
 >
 <h1>Creative Developer</h1>
 <p>Building exceptional digital experiences</p>
 </div>
 <div
 data-scroll
 data-scroll-speed="2"
 className="hero-foreground"
 >
 <div className="floating-shapes"></div>
 </div>
</header>

This three-layer approach creates convincing depth, with the background moving slowly, content at normal speed, and foreground elements appearing to float above the main content.

Sticky Scroll and Fixed Positioning

Sticky scroll sections hold elements in place while surrounding content scrolls past, creating opportunities for extended content presentation without losing context. Locomotive Scroll handles sticky positioning through its scroll section behavior and can be enhanced with CSS sticky positioning or JavaScript-controlled transforms as shown in LogRocket's examples.

Creating Sticky Sections

The sticky scroll effect requires careful HTML structure and often combines Locomotive Scroll's section handling with CSS sticky positioning:

<section data-scroll-section id="sticky-section" className="sticky-container">
 <div className="sticky-content">
 <h2 data-scroll data-scroll-sticky data-scroll-target="#sticky-section">
 Project Showcase
 </h2>
 <div className="scroll-content">
 <p>As you scroll through this section...</p>
 <p>The heading remains fixed in view...</p>
 <p>While different project details scroll past...</p>
 </div>
 </div>
</section>

The data-scroll-sticky attribute combined with data-scroll-target creates a sticky element that stays fixed within its target container as the user scrolls.

Practical Applications for Portfolios

Sticky scroll is ideal for project showcases where you want a project title or key image to remain visible while detailed information scrolls into view. This pattern allows visitors to maintain context while exploring extensive project details, improving the browsing experience for content-rich portfolios.

Common applications include skill lists that remain visible while project descriptions scroll, client logos that stay fixed while case study content scrolls, and navigation elements that provide persistent access to page sections. When building comprehensive portfolio websites, these techniques help organize large amounts of content while maintaining visual appeal.

Scroll-Triggered Animations

Beyond continuous effects like parallax, Locomotive Scroll provides classes that trigger when elements enter or exit the viewport. These scroll-triggered animations create engaging reveal effects as users explore the page as detailed in LogRocket's guide.

Available Scroll Classes

Locomotive Scroll adds several classes to elements based on their scroll position:

  • data-scroll: Base attribute for any scroll animation
  • data-scroll-show: Element appears when scrolled into view
  • data-scroll-hide: Element disappears when scrolled into view
  • data-scroll-repeat: Animation repeats each time element enters viewport

These classes can be combined with CSS transitions to create smooth reveal effects:

.reveal-element {
 opacity: 0;
 transform: translateY(50px);
 transition: opacity 0.6s ease, transform 0.6s ease;
}

[data-scroll-show] .reveal-element {
 opacity: 1;
 transform: translateY(0);
}

Combining with React State

For more complex animations, scroll position can be captured and used to drive React state changes:

import { useState, useEffect, useRef } from 'react';

function AnimatedSection() {
 const [isVisible, setIsVisible] = useState(false);
 const sectionRef = useRef(null);

 useEffect(() => {
 const scroll = new LocomotiveScroll();

 scroll.on('scroll', (args) => {
 const element = sectionRef.current;
 if (element) {
 const rect = element.getBoundingClientRect();
 setIsVisible(rect.top < window.innerHeight && rect.bottom > 0);
 }
 });

 return () => scroll.destroy();
 }, []);

 return (
 <section
 ref={sectionRef}
 data-scroll
 data-scroll-class={isVisible ? 'visible' : ''}
 >
 <div className={`content ${isVisible ? 'animate-in' : ''}`}>
 <h2>Animated Content</h2>
 <p>This content animates when it enters the viewport.</p>
 </div>
 </section>
 );
}

React Integration Best Practices

Integrating Locomotive Scroll with React requires attention to component lifecycle, cleanup, and proper initialization timing. Following these patterns ensures reliable behavior and prevents memory leaks as recommended by Pulse Studio Lab.

Using useEffect for Initialization

The useEffect hook is the correct place to initialize Locomotive Scroll because it runs after the component mounts and the DOM is ready. The effect should return a cleanup function that destroys the scroll instance to prevent memory leaks and unexpected behavior during component unmounting:

import { useEffect, useRef } from 'react';
import LocomotiveScroll from 'locomotive-scroll';

function PortfolioLayout({ children }) {
 const scrollRef = useRef(null);
 const scrollInstance = useRef(null);

 useEffect(() => {
 if (!scrollRef.current) return;

 scrollInstance.current = new LocomotiveScroll({
 el: scrollRef.current,
 smooth: true,
 multiplier: 1,
 lerp: 0.1,
 smartphone: {
 smooth: true
 },
 tablet: {
 smooth: true
 }
 });

 // Update scroll after content loads
 setTimeout(() => {
 scrollInstance.current.update();
 }, 100);

 return () => {
 if (scrollInstance.current) {
 scrollInstance.current.destroy();
 }
 };
 }, []);

 return (
 <main ref={scrollRef} data-scroll-container>
 {children}
 </main>
 );
}

Handling Dynamic Content

When content changes dynamically, such as when filtering projects or loading more items, Locomotive Scroll needs to be notified to recalculate scroll positions:

function ProjectGallery({ projects }) {
 const scrollRef = useRef(null);

 useEffect(() => {
 const scroll = new LocomotiveScroll({
 el: scrollRef.current,
 smooth: true
 });

 // Update scroll when projects change
 scroll.update();

 return () => scroll.destroy();
 }, [projects]);

 return (
 <main ref={scrollRef} data-scroll-container>
 <div className="project-grid">
 {projects.map(project => (
 <article key={project.id} data-scroll-section>
 <ProjectCard project={project} />
 </article>
 ))}
 </div>
 </main>
 );
}

The update() method recalculates all scroll positions and element dimensions, ensuring animations work correctly after content changes.

Performance and Accessibility

While Locomotive Scroll creates impressive visual effects, developers must balance these with performance and accessibility requirements. Heavy scroll animations can impact page load times and device battery life, while aggressive scroll interception can frustrate users who rely on assistive technologies as noted by Pulse Studio Lab.

Reduced Motion Preference

Users who experience discomfort from motion animations should have the option to disable smooth scrolling. React applications can detect this preference and conditionally initialize Locomotive Scroll:

import { useEffect, useState } from 'react';
import LocomotiveScroll from 'locomotive-scroll';

function PortfolioPage() {
 const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);

 useEffect(() => {
 const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
 setPrefersReducedMotion(mediaQuery.matches);

 const handler = (event) => setPrefersReducedMotion(event.matches);
 mediaQuery.addEventListener('change', handler);

 return () => mediaQuery.removeEventListener('change', handler);
 }, []);

 useEffect(() => {
 if (prefersReducedMotion) return;

 const scroll = new LocomotiveScroll({
 el: document.querySelector('[data-scroll-container]'),
 smooth: true,
 lerp: 0.1
 });

 return () => scroll.destroy();
 }, [prefersReducedMotion]);

 return (
 <main data-scroll-container>
 {/* Page content */}
 </main>
 );
}

This pattern respects user preferences while still providing enhanced experiences for those who want them.

Mobile Considerations

Mobile devices present unique challenges for custom scroll libraries. Touch interactions, limited processing power, and variable network conditions all affect performance. Locomotive Scroll provides mobile-specific options to optimize behavior:

new LocomotiveScroll({
 el: containerRef.current,
 smooth: true,
 smartphone: {
 smooth: true,
 multiplier: 0.5
 },
 tablet: {
 smooth: true,
 multiplier: 0.75
 }
});

Reducing the multiplier on mobile devices creates a more subdued effect that performs better on constrained hardware while still providing an enhanced experience.

Performance Optimization Tips

Several strategies can improve performance when using Locomotive Scroll:

  • Lazy load animations: Only apply scroll effects to elements near the viewport
  • Simplify parallax layers: Use fewer layers with larger depth differences
  • Optimize images: Compress and properly size all visual elements
  • Debounce updates: Limit how often scroll position calculations occur
  • Use CSS transforms: Prefer transform properties over position changes
Key Locomotive Scroll Features for Portfolios

Essential capabilities for creating engaging scrolling experiences

Smooth Scrolling

Creates fluid, premium scroll behavior that transforms standard scrolling into cinematic experiences

Parallax Effects

Multi-layer depth animations where elements move at different speeds to create visual interest

Sticky Sections

Hold elements in place while content scrolls past, ideal for project showcases and case studies

Scroll Triggers

Reveal animations that activate when elements enter the viewport, creating engaging reveal effects

Conclusion

Locomotive Scroll provides React developers with powerful capabilities for creating engaging, animated portfolio websites. From smooth scrolling and parallax effects to sticky sections and scroll-triggered reveals, the library enables experiences that distinguish exceptional portfolios from ordinary ones.

When implementing Locomotive Scroll, focus on creating meaningful animations that enhance rather than distract from your content. Respect user preferences for reduced motion, ensure accessibility, and optimize performance for all devices. With these considerations in mind, Locomotive Scroll becomes a valuable tool for creating portfolio experiences that showcase both your technical capabilities and your commitment to user experience excellence.

The key to successful implementation lies in restraint: use animations purposefully, test thoroughly across devices and browsers, and always prioritize content accessibility over visual flourish. When balanced correctly, Locomotive Scroll transforms standard scrolling into an engaging narrative experience that helps visitors connect with your work.

Key Takeaways

  • Locomotive Scroll transforms standard scrolling into smooth, animated experiences
  • Proper CSS setup is essential for correct functionality
  • Use speed values to create parallax depth effects
  • Sticky scroll maintains context during content exploration
  • Always respect reduced motion preferences for accessibility
  • Test thoroughly across devices and browsers

Frequently Asked Questions

Ready to Build an Animated Portfolio?

Our team specializes in creating engaging, performant portfolio websites with modern animation techniques.