
Barba Js
- 1.4k installs
- 629 repo stars
- Updated November 20, 2025
- freshtechbro/claudedesignskills
barba-js provides documented workflows for Page transitions library for creating fluid, smooth transitions between website pages. Use this skill when implementing page transitions, creating SPA-like expe
About
The barba-js skill page transitions library for creating fluid smooth transitions between website pages Use this skill when implementing page transitions creating SPA-like experiences adding animated route changes or building websites with smooth navigation Triggers on tasks involving Barba js page transitions routing view management transition hooks GSAP integration or smooth page navigation Works with gsap-scrolltrigger for transition animations Barba js Modern page transition library for creating fluid smooth transitions between website pages Barba js makes multi-page websites feel like Single Page Applications SPAs by hijacking navigation and managing transitions without full page reloads Overview Barba js is a lightweight 7kb minified and compressed JavaScript library that intercepts navigation between pages fetches new content via AJAX and smoothly transitions between old and new containers It reduces page load delays and HTTP requests while maintaining the benefits of traditional multi-page architecture Core Features Smooth page transitions without full reloads Lifecycle hooks for precise control over transition phases View-based logic for page-specific behaviors Built-in r.
- Smooth page transitions without full reloads
- Lifecycle hooks for precise control over transition phases
- View-based logic for page-specific behaviors
- Built-in routing with @barba/router plugin
- Extensible plugin system
Barba Js by the numbers
- 1,385 all-time installs (skills.sh)
- +92 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #331 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
barba-js capabilities & compatibility
- Capabilities
- smooth page transitions without full reloads · lifecycle hooks for precise control over transit · view based logic for page specific behaviors · built in routing with @barba/router plugin · extensible plugin system
- Use cases
- documentation
What barba-js says it does
# Barba.js Modern page transition library for creating fluid, smooth transitions between website pages.
Barba.js makes multi-page websites feel like Single Page Applications (SPAs) by hijacking navigation and managing transitions without full page reloads.
npx skills add https://github.com/freshtechbro/claudedesignskills --skill barba-jsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.4k |
|---|---|
| repo stars | ★ 629 |
| Security audit | 1 / 3 scanners passed |
| Last updated | November 20, 2025 |
| Repository | freshtechbro/claudedesignskills ↗ |
How do I use barba-js for the task described in its SKILL.md triggers?
Page transitions library for creating fluid, smooth transitions between website pages. Use this skill when implementing page transitions, creating SPA-like experiences, adding animated route changes,.
Who is it for?
Teams invoking barba-js when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
Page transitions library for creating fluid, smooth transitions between website pages. Use this skill when implementing page transitions, creating SPA-like experiences, adding animated route changes, or building websites
What you get
Step-by-step guidance grounded in barba-js documentation and reference files.
- multi-page HTML project
- Barba transition CSS and JS
- example about and contact pages
By the numbers
- Generates 3 starter HTML pages: index.html, about.html, and contact.html
- CLI supports `--name` and `--transition fade` flags in `project_setup.py`
Files
Barba.js
Modern page transition library for creating fluid, smooth transitions between website pages. Barba.js makes multi-page websites feel like Single Page Applications (SPAs) by hijacking navigation and managing transitions without full page reloads.
Overview
Barba.js is a lightweight (7kb minified and compressed) JavaScript library that intercepts navigation between pages, fetches new content via AJAX, and smoothly transitions between old and new containers. It reduces page load delays and HTTP requests while maintaining the benefits of traditional multi-page architecture.
Core Features:
- Smooth page transitions without full reloads
- Lifecycle hooks for precise control over transition phases
- View-based logic for page-specific behaviors
- Built-in routing with @barba/router plugin
- Extensible plugin system
- Small footprint and high performance
- Framework-agnostic (works with vanilla JS, GSAP, anime.js, etc.)
Core Concepts
1. Wrapper, Container, and Namespace
Barba.js uses a specific DOM structure to manage transitions:
HTML Structure:
<body data-barba="wrapper">
<!-- Static elements (header, nav) stay outside container -->
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<!-- Dynamic content goes in container -->
<main data-barba="container" data-barba-namespace="home">
<!-- This content changes on navigation -->
<h1>Home Page</h1>
<p>Content that will transition out...</p>
</main>
<!-- Static footer outside container -->
<footer>© 2025</footer>
</body>Three Key Elements:
1. Wrapper (data-barba="wrapper")
- Outermost container
- Everything inside wrapper but outside container stays persistent
- Ideal for headers, navigation, footers that don't change
2. Container (data-barba="container")
- Dynamic content area that updates on navigation
- Only this section gets replaced during transitions
- Must exist on every page
3. Namespace (data-barba-namespace="home")
- Unique identifier for each page type
- Used in transition rules and view logic
- Examples: "home", "about", "product", "blog-post"
2. Transition Lifecycle
Barba.js follows a precise lifecycle for each navigation:
Default Async Flow: 1. User clicks link 2. Barba intercepts navigation 3. Prefetch next page (via AJAX) 4. Cache new content 5. Leave hook - Animate current page out 6. Wait for leave animation to complete 7. Remove old container, insert new container 8. Enter hook - Animate new page in 9. Wait for enter animation to complete 10. Update browser history
Sync Flow (with sync: true): 1. User clicks link 2. Barba intercepts navigation 3. Prefetch next page 4. Wait for new page to load 5. Leave and Enter hooks run simultaneously (crossfade effect) 6. Swap containers 7. Update browser history
3. Hooks
Barba provides 11 lifecycle hooks for controlling transitions:
Hook Execution Order:
Initial page load:
beforeOnce → once → afterOnce
Every navigation:
before → beforeLeave → leave → afterLeave →
beforeEnter → enter → afterEnter → afterHook Types:
- Global hooks: Run on every transition (
barba.hooks.before()) - Transition hooks: Defined within specific transition objects
- View hooks: Defined within view objects for page-specific logic
Common Hook Use Cases:
beforeLeave- Reset scroll position, prepare animationsleave- Animate current page outafterLeave- Clean up old pagebeforeEnter- Prepare new page (hide elements, set initial states)enter- Animate new page inafterEnter- Initialize page scripts, analytics tracking
4. Views
Views are page-specific logic containers that run based on namespace:
barba.init({
views: [{
namespace: 'home',
beforeEnter() {
// Home-specific setup
console.log('Entering home page');
},
afterEnter() {
// Initialize home page features
initHomeSlider();
}
}, {
namespace: 'product',
beforeEnter() {
console.log('Entering product page');
},
afterEnter() {
initProductGallery();
}
}]
});Common Patterns
1. Basic Setup
Installation:
npm install --save-dev @barba/core
# or
yarn add @barba/core --devMinimal Configuration:
import barba from '@barba/core';
barba.init({
transitions: [{
name: 'default',
leave({ current }) {
// Fade out current page
return gsap.to(current.container, {
opacity: 0,
duration: 0.5
});
},
enter({ next }) {
// Fade in new page
return gsap.from(next.container, {
opacity: 0,
duration: 0.5
});
}
}]
});2. Fade Transition (Async)
Classic fade-out, fade-in transition:
import barba from '@barba/core';
import gsap from 'gsap';
barba.init({
transitions: [{
name: 'fade',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.5,
ease: 'power2.inOut'
});
},
async enter({ next }) {
// Start invisible
gsap.set(next.container, { opacity: 0 });
// Fade in
await gsap.to(next.container, {
opacity: 1,
duration: 0.5,
ease: 'power2.inOut'
});
}
}]
});3. Crossfade Transition (Sync)
Simultaneous fade between pages:
barba.init({
transitions: [{
name: 'crossfade',
sync: true, // Enable sync mode
leave({ current }) {
return gsap.to(current.container, {
opacity: 0,
duration: 0.8,
ease: 'power2.inOut'
});
},
enter({ next }) {
return gsap.from(next.container, {
opacity: 0,
duration: 0.8,
ease: 'power2.inOut'
});
}
}]
});4. Slide Transition with Overlap
Slide old page out, new page in with overlap:
barba.init({
transitions: [{
name: 'slide',
sync: true,
leave({ current }) {
return gsap.to(current.container, {
x: '-100%',
duration: 0.7,
ease: 'power3.inOut'
});
},
enter({ next }) {
// Start off-screen right
gsap.set(next.container, { x: '100%' });
// Slide in from right
return gsap.to(next.container, {
x: '0%',
duration: 0.7,
ease: 'power3.inOut'
});
}
}]
});5. Transition Rules (Conditional Transitions)
Define different transitions based on navigation context:
barba.init({
transitions: [
// Home to any page: fade
{
name: 'from-home-fade',
from: { namespace: 'home' },
leave({ current }) {
return gsap.to(current.container, {
opacity: 0,
duration: 0.5
});
},
enter({ next }) {
return gsap.from(next.container, {
opacity: 0,
duration: 0.5
});
}
},
// Product to product: slide left
{
name: 'product-to-product',
from: { namespace: 'product' },
to: { namespace: 'product' },
leave({ current }) {
return gsap.to(current.container, {
x: '-100%',
duration: 0.6
});
},
enter({ next }) {
gsap.set(next.container, { x: '100%' });
return gsap.to(next.container, {
x: '0%',
duration: 0.6
});
}
},
// Default fallback
{
name: 'default',
leave({ current }) {
return gsap.to(current.container, {
opacity: 0,
duration: 0.3
});
},
enter({ next }) {
return gsap.from(next.container, {
opacity: 0,
duration: 0.3
});
}
}
]
});6. Router Plugin for Route-Based Transitions
Use @barba/router for route-specific transitions:
Installation:
npm install --save-dev @barba/routerUsage:
import barba from '@barba/core';
import barbaPrefetch from '@barba/prefetch';
import barbaRouter from '@barba/router';
// Define routes
barbaRouter.init({
routes: [
{ path: '/', name: 'home' },
{ path: '/about', name: 'about' },
{ path: '/products/:id', name: 'product' }, // Dynamic segment
{ path: '/blog/:category/:slug', name: 'blog-post' }
]
});
barba.use(barbaRouter);
barba.use(barbaPrefetch); // Optional: prefetch on hover
barba.init({
transitions: [{
name: 'product-transition',
to: { route: 'product' }, // Trigger on route name
leave({ current }) {
return gsap.to(current.container, {
scale: 0.95,
opacity: 0,
duration: 0.5
});
},
enter({ next }) {
return gsap.from(next.container, {
scale: 1.05,
opacity: 0,
duration: 0.5
});
}
}]
});7. Loading Indicator
Show loading state during page fetch:
barba.init({
transitions: [{
async leave({ current }) {
// Show loader
const loader = document.querySelector('.loader');
gsap.set(loader, { display: 'flex', opacity: 0 });
gsap.to(loader, { opacity: 1, duration: 0.3 });
// Fade out page
await gsap.to(current.container, {
opacity: 0,
duration: 0.5
});
},
async enter({ next }) {
// Hide loader
const loader = document.querySelector('.loader');
await gsap.to(loader, { opacity: 0, duration: 0.3 });
gsap.set(loader, { display: 'none' });
// Fade in page
await gsap.from(next.container, {
opacity: 0,
duration: 0.5
});
}
}]
});Integration Patterns
GSAP Integration
Barba.js works seamlessly with GSAP for animations:
Timeline-Based Transitions:
import barba from '@barba/core';
import gsap from 'gsap';
barba.init({
transitions: [{
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelector('h1'), {
y: -50,
opacity: 0,
duration: 0.3
})
.to(current.container.querySelector('.content'), {
y: -30,
opacity: 0,
duration: 0.3
}, '-=0.2')
.to(current.container, {
opacity: 0,
duration: 0.2
});
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
// Set initial states
gsap.set(next.container, { opacity: 0 });
gsap.set(next.container.querySelector('h1'), { y: 50, opacity: 0 });
gsap.set(next.container.querySelector('.content'), { y: 30, opacity: 0 });
tl.to(next.container, {
opacity: 1,
duration: 0.2
})
.to(next.container.querySelector('h1'), {
y: 0,
opacity: 1,
duration: 0.5,
ease: 'power3.out'
})
.to(next.container.querySelector('.content'), {
y: 0,
opacity: 1,
duration: 0.5,
ease: 'power3.out'
}, '-=0.3');
await tl.play();
}
}]
});Reference gsap-scrolltrigger skill for advanced GSAP integration patterns.
View-Specific Initialization
Initialize libraries or scripts per page:
barba.init({
views: [
{
namespace: 'home',
afterEnter() {
// Initialize home page features
initHomepageSlider();
initParallaxEffects();
},
beforeLeave() {
// Clean up
destroyHomepageSlider();
}
},
{
namespace: 'gallery',
afterEnter() {
initLightbox();
initMasonry();
},
beforeLeave() {
destroyLightbox();
}
}
]
});Analytics Tracking
Track page views on navigation:
barba.hooks.after(() => {
// Google Analytics
if (typeof gtag !== 'undefined') {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: window.location.pathname
});
}
// Or use data layer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'pageview',
page: window.location.pathname
});
});Third-Party Script Re-Initialization
Re-run scripts after page transitions:
barba.hooks.after(() => {
// Re-initialize third-party widgets
if (typeof twttr !== 'undefined') {
twttr.widgets.load(); // Twitter widgets
}
if (typeof FB !== 'undefined') {
FB.XFBML.parse(); // Facebook widgets
}
// Re-run syntax highlighting
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
});Performance Optimization
1. Prefetching
Use @barba/prefetch to load pages on hover:
npm install --save-dev @barba/prefetchimport barba from '@barba/core';
import barbaPrefetch from '@barba/prefetch';
barba.use(barbaPrefetch);
barba.init({
// Prefetch fires on link hover by default
prefetch: {
root: null, // Observe all links
timeout: 3000 // Cache timeout in ms
}
});2. Prevent Layout Shift
Set container min-height to prevent content jump:
[data-barba="container"] {
min-height: 100vh;
/* Or use viewport height minus header/footer */
min-height: calc(100vh - 80px - 60px);
}3. Optimize Animations
Use GPU-accelerated properties:
// ✅ Good - GPU accelerated
gsap.to(element, {
opacity: 0,
x: -100,
scale: 0.9,
rotation: 45
});
// ❌ Avoid - causes reflow/repaint
gsap.to(element, {
width: '50%',
height: '300px',
top: '100px'
});4. Clean Up Event Listeners
Remove listeners in beforeLeave or view hooks:
barba.init({
views: [{
namespace: 'home',
afterEnter() {
// Add listeners
this.clickHandler = () => console.log('clicked');
document.querySelector('.btn').addEventListener('click', this.clickHandler);
},
beforeLeave() {
// Remove listeners
document.querySelector('.btn').removeEventListener('click', this.clickHandler);
}
}]
});5. Lazy Load Images
Defer image loading until after transition:
barba.init({
transitions: [{
async enter({ next }) {
// Complete transition first
await gsap.from(next.container, {
opacity: 0,
duration: 0.5
});
// Then load images
const images = next.container.querySelectorAll('img[data-src]');
images.forEach(img => {
img.src = img.dataset.src;
img.removeAttribute('data-src');
});
}
}]
});Common Pitfalls
1. Forgetting to Return Promises
Problem: Transitions complete instantly without waiting for animations.
Solution: Always return promises or use async/await:
// ❌ Wrong - animation starts but doesn't wait
leave({ current }) {
gsap.to(current.container, { opacity: 0, duration: 0.5 });
}
// ✅ Correct - returns promise
leave({ current }) {
return gsap.to(current.container, { opacity: 0, duration: 0.5 });
}
// ✅ Also correct - async/await
async leave({ current }) {
await gsap.to(current.container, { opacity: 0, duration: 0.5 });
}2. Not Preventing Default Link Behavior
Problem: Some links cause full page reloads.
Solution: Barba automatically prevents default on internal links, but you may need to exclude external links:
barba.init({
prevent: ({ href }) => {
// Allow external links
if (href.indexOf('http') > -1 && href.indexOf(window.location.host) === -1) {
return true;
}
return false;
}
});3. CSS Conflicts Between Pages
Problem: Old page CSS affects new page layout during transition.
Solution: Use namespace-specific CSS or reset styles:
/* Namespace-specific styles */
[data-barba-namespace="home"] .hero {
background: blue;
}
[data-barba-namespace="about"] .hero {
background: red;
}Or reset in beforeEnter:
beforeEnter({ next }) {
// Reset scroll position
window.scrollTo(0, 0);
// Reset any global state
document.body.classList.remove('menu-open');
}4. Not Updating Document Title and Meta Tags
Problem: Page title and meta tags don't update on navigation.
Solution: Use @barba/head plugin or update manually:
npm install --save-dev @barba/headimport barba from '@barba/core';
import barbaHead from '@barba/head';
barba.use(barbaHead);
barba.init({
// Head plugin automatically updates <head> tags
});Or manually:
barba.hooks.after(({ next }) => {
// Update title
document.title = next.html.querySelector('title').textContent;
// Update meta tags
const newMeta = next.html.querySelectorAll('meta');
newMeta.forEach(meta => {
const name = meta.getAttribute('name') || meta.getAttribute('property');
if (name) {
const existing = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`);
if (existing) {
existing.setAttribute('content', meta.getAttribute('content'));
}
}
});
});5. Animation Flicker on Enter
Problem: New page flashes visible before enter animation starts.
Solution: Set initial invisible state in CSS or beforeEnter:
/* CSS approach */
[data-barba="container"] {
opacity: 0;
}
[data-barba="container"].is-visible {
opacity: 1;
}// JavaScript approach
beforeEnter({ next }) {
gsap.set(next.container, { opacity: 0 });
}6. Sync Transitions Without Proper Positioning
Problem: Sync transitions cause layout shift as containers stack.
Solution: Position containers absolutely during transition:
[data-barba="wrapper"] {
position: relative;
}
[data-barba="container"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
}Or manage in JavaScript:
barba.init({
transitions: [{
sync: true,
beforeLeave({ current }) {
gsap.set(current.container, {
position: 'absolute',
top: 0,
left: 0,
width: '100%'
});
}
}]
});Resources
This skill includes:
scripts/
Executable utilities for common Barba.js tasks:
transition_generator.py- Generate transition boilerplate codeproject_setup.py- Initialize Barba.js project structure
references/
Detailed documentation:
api_reference.md- Complete Barba.js API (hooks, transitions, views, router)hooks_guide.md- All 11 hooks with execution order and use casesgsap_integration.md- GSAP animation patterns for Barba transitionstransition_patterns.md- Common transition implementations
assets/
Templates and starter projects:
starter_barba/- Complete Barba.js + GSAP starter templateexamples/- Real-world transition implementations
Related Skills
- gsap-scrolltrigger - For advanced GSAP animations in transitions
- locomotive-scroll - Can be combined with Barba for smooth scrolling between pages
- motion-framer - Alternative approach for React-based page transitions
Barba.js Assets
This directory contains information about assets and starter templates for Barba.js projects.
Starter Templates
Complete Barba.js starter templates are generated automatically by the project_setup.py script.
Usage
Run the project setup script to generate a complete Barba.js project:
../scripts/project_setup.pyOr in CLI mode:
../scripts/project_setup.py --name my-project --transition fadeGenerated Project Structure
The script creates a complete project with:
my-project/
├── index.html # Home page with Barba structure
├── about.html # Example about page
├── contact.html # Example contact page
├── src/
│ ├── main.js # Barba.js initialization with transitions
│ └── style.css # Complete styling with transition support
├── package.json # Dependencies (@barba/core, gsap, vite)
├── vite.config.js # Vite configuration for multi-page app
└── README.md # Project-specific documentationAvailable Transition Types
The generated project includes one of these transitions:
1. fade - Simple fade in/out 2. slide - Horizontal slide transition 3. scale - Zoom with fade effect 4. stagger - Staggered element animations 5. curtain - Curtain overlay effect
Features
Generated projects include:
- Complete HTML structure with proper
data-barbaattributes - Responsive navigation that persists across page transitions
- GSAP-powered animations
- Loading indicator
- Transition curtain element
- Mobile-responsive styling
- Vite dev server and build setup
- Example pages demonstrating namespace-based routing
Customization
After generating a project:
1. Modify transitions in src/main.js 2. Add custom styles in src/style.css 3. Create additional pages following the same structure 4. Update vite.config.js to include new pages in build
Example HTML Structure
All generated pages follow this structure:
<body data-barba="wrapper">
<!-- Persistent header (outside container) -->
<header class="site-header">
<nav><!-- Navigation links --></nav>
</header>
<!-- Dynamic content (inside container) -->
<main data-barba="container" data-barba-namespace="page-name">
<!-- Page content that transitions -->
</main>
<!-- Persistent footer (outside container) -->
<footer class="site-footer"><!-- Footer content --></footer>
<!-- Transition elements -->
<div class="page-loader">Loading...</div>
<div class="transition-curtain"></div>
<script type="module" src="/src/main.js"></script>
</body>Development Workflow
1. Generate project: ../scripts/project_setup.py --name my-site 2. Navigate to project: cd my-site 3. Install dependencies: npm install (auto-run unless --no-install) 4. Start dev server: npm run dev 5. Open browser: http://localhost:5173 6. Build for production: npm run build
Additional Examples
For custom transition code snippets, use the transition generator:
../scripts/transition_generator.pyThis generates just the JavaScript transition code that you can copy into your project.
Manual Setup (Without Scripts)
If you prefer to set up manually:
1. Install Dependencies
npm install --save-dev @barba/core gsap2. Create HTML Structure
Add Barba attributes to your HTML:
<body data-barba="wrapper">
<main data-barba="container" data-barba-namespace="home">
<!-- Your content -->
</main>
</body>3. Initialize Barba
Create JavaScript file:
import barba from '@barba/core';
import gsap from 'gsap';
barba.init({
transitions: [{
name: 'fade',
async leave({ current }) {
await gsap.to(current.container, { opacity: 0 });
},
async enter({ next }) {
await gsap.from(next.container, { opacity: 0 });
}
}]
});4. Add to HTML
<script type="module" src="/path/to/your/script.js"></script>Resources
- SKILL.md - Complete Barba.js guide with patterns and examples
- references/api_reference.md - Full API documentation
- references/hooks_guide.md - Lifecycle hooks reference
- references/gsap_integration.md - GSAP animation patterns
- references/transition_patterns.md - Ready-to-use transition code
- scripts/transition_generator.py - Generate custom transition code
- scripts/project_setup.py - Generate complete starter projects
Barba.js API Reference
Complete reference for Barba.js core API, plugins, and configuration options.
Table of Contents
- Core API
- barba.init()
- barba.go()
- barba.hooks
- barba.history
- barba.url
- barba.use()
- Transitions
- Views
- Hooks
- Data Object
- Router Plugin
- Prefetch Plugin
- CSS Plugin
- Head Plugin
---
Core API
barba.init()
Initialize Barba with configuration options.
Syntax:
barba.init({
debug: false,
logLevel: 'off',
timeout: 2000,
cacheIgnore: false,
preventRunning: true,
prevent: null,
requestError: null,
schema: {
prefix: 'data-barba',
wrapper: 'wrapper',
container: 'container',
namespace: 'namespace'
},
transitions: [],
views: []
});Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
debug | boolean | false | Enable debug mode (logs to console) |
logLevel | string | 'off' | Logging level: 'off', 'error', 'warning', 'info', 'debug' |
timeout | number | 2000 | Request timeout in milliseconds |
cacheIgnore | `boolean\ | string\ | function` |
preventRunning | boolean | true | Prevent transitions if one is already running |
prevent | function | null | Custom function to prevent Barba on specific links |
requestError | function | null | Custom request error handler |
schema | object | See above | Custom data attribute names |
transitions | array | [] | Array of transition objects |
views | array | [] | Array of view objects |
Examples:
// Minimal setup
barba.init();
// Custom configuration
barba.init({
debug: true,
timeout: 5000,
prevent: ({ el, href }) => {
// Prevent external links
return href.indexOf('http') > -1 && href.indexOf(window.location.host) === -1;
},
requestError: (trigger, action, url, response) => {
// Redirect to 404 page
if (action === 'click' && response.status === 404) {
barba.go('/404');
}
}
});
// Custom schema (use different data attributes)
barba.init({
schema: {
prefix: 'data-custom',
wrapper: 'page-wrapper',
container: 'page-container',
namespace: 'page-type'
}
});barba.go()
Programmatically navigate to a URL.
Syntax:
barba.go(href, trigger, event);Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
href | string | - | Target URL (relative or absolute) |
trigger | string | 'barba' | Trigger identifier (appears in data.trigger) |
event | object | null | Event object (appears in data.event) |
Examples:
// Navigate to URL
barba.go('/about');
// Navigate with custom trigger
barba.go('/contact', 'custom-button');
// Navigate with event data
barba.go('/products/123', 'product-link', { productId: 123 });
// Conditional navigation
if (userLoggedIn) {
barba.go('/dashboard');
} else {
barba.go('/login');
}barba.hooks
Global hooks object for registering lifecycle hooks.
Available Hooks:
All hooks receive a data object as parameter:
barba.hooks.beforeOnce(data => { /* ... */ });
barba.hooks.once(data => { /* ... */ });
barba.hooks.afterOnce(data => { /* ... */ });
barba.hooks.before(data => { /* ... */ });
barba.hooks.beforeLeave(data => { /* ... */ });
barba.hooks.leave(data => { /* ... */ });
barba.hooks.afterLeave(data => { /* ... */ });
barba.hooks.beforeEnter(data => { /* ... */ });
barba.hooks.enter(data => { /* ... */ });
barba.hooks.afterEnter(data => { /* ... */ });
barba.hooks.after(data => { /* ... */ });Examples:
// Reset scroll on every transition
barba.hooks.beforeEnter(() => {
window.scrollTo(0, 0);
});
// Track page views
barba.hooks.after(({ next }) => {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: next.url.path
});
});
// Show/hide loading indicator
barba.hooks.before(() => {
document.querySelector('.loader').classList.add('active');
});
barba.hooks.after(() => {
document.querySelector('.loader').classList.remove('active');
});
// Re-initialize scripts
barba.hooks.afterEnter(() => {
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
});barba.history
Access browser history and navigation state.
Properties:
| Property | Type | Description |
|---|---|---|
previous | object | Previous page data |
current | object | Current page data |
size | number | Number of items in history |
direction | string | Navigation direction: 'forward', 'back', or null |
Methods:
| Method | Description |
|---|---|
add(url, trigger) | Add entry to history |
cancel() | Cancel current transition |
Examples:
// Check navigation direction
barba.hooks.before(({ current, next }) => {
const direction = barba.history.direction;
if (direction === 'back') {
console.log('User went back');
} else if (direction === 'forward') {
console.log('User went forward');
} else {
console.log('Normal navigation');
}
});
// Access previous page
const previousUrl = barba.history.previous.url.href;
const previousNamespace = barba.history.previous.namespace;
// Cancel transition programmatically
if (someCondition) {
barba.history.cancel();
}barba.url
URL manipulation utilities.
Methods:
| Method | Parameters | Returns | Description |
|---|---|---|---|
getAbsoluteHref(href) | string | string | Convert relative URL to absolute |
getHref(url) | string | string | Get clean href (without hash) |
getOrigin(url) | string | string | Get origin from URL |
getPath(url) | string | string | Get path from URL |
getPathname(url) | string | string | Get pathname from URL |
getPort(url) | string | string | Get port from URL |
Examples:
// Get absolute URL
const absolute = barba.url.getAbsoluteHref('/about');
// Returns: "https://example.com/about"
// Get path without query/hash
const path = barba.url.getPath('/products?id=123#details');
// Returns: "/products"
// Get pathname
const pathname = barba.url.getPathname('https://example.com/blog/post-1');
// Returns: "/blog/post-1"barba.use()
Register Barba plugins.
Syntax:
barba.use(plugin, options);Parameters:
| Parameter | Type | Description |
|---|---|---|
plugin | object | Plugin object with install() method |
options | object | Plugin-specific options |
Examples:
import barba from '@barba/core';
import barbaRouter from '@barba/router';
import barbaPrefetch from '@barba/prefetch';
import barbaHead from '@barba/head';
// Register router
barba.use(barbaRouter, {
routes: [
{ path: '/', name: 'home' },
{ path: '/about', name: 'about' }
]
});
// Register prefetch
barba.use(barbaPrefetch);
// Register head plugin
barba.use(barbaHead);
barba.init();---
Transitions
Transition objects define how pages animate during navigation.
Transition Object Structure:
{
name: 'transition-name',
from: { /* rules */ },
to: { /* rules */ },
sync: false,
once() { /* ... */ },
leave() { /* ... */ },
enter() { /* ... */ },
// ... other hooks
}Properties:
| Property | Type | Default | Description |
|---|---|---|---|
name | string | - | Unique transition identifier |
from | object | - | Rules for leaving page |
to | object | - | Rules for entering page |
sync | boolean | false | Sync mode (play leave/enter simultaneously) |
Rules:
Rules determine when a transition applies:
// Namespace rule
{ namespace: 'home' }
{ namespace: ['home', 'about'] }
// Route rule (requires @barba/router)
{ route: 'product' }
// Custom rule
{ custom: ({ current, next }) => current.namespace === next.namespace }Rule Priority (highest to lowest): 1. custom 2. route (requires @barba/router) 3. namespace
Transition Selection Examples:
barba.init({
transitions: [
// Only from home
{
name: 'from-home',
from: { namespace: 'home' }
},
// Only to about
{
name: 'to-about',
to: { namespace: 'about' }
},
// From home to about
{
name: 'home-to-about',
from: { namespace: 'home' },
to: { namespace: 'about' }
},
// Multiple namespaces
{
name: 'from-pages',
from: { namespace: ['about', 'contact', 'services'] }
},
// Custom rule
{
name: 'same-namespace',
custom: ({ current, next }) => current.namespace === next.namespace
},
// Always matches (fallback)
{
name: 'default'
}
]
});Sync Mode:
// Async (default): leave → wait → swap → enter
{
sync: false,
async leave({ current }) {
await gsap.to(current.container, { opacity: 0 });
},
async enter({ next }) {
await gsap.from(next.container, { opacity: 0 });
}
}
// Sync: wait → (leave + enter simultaneously) → swap
{
sync: true,
leave({ current }) {
return gsap.to(current.container, { opacity: 0 });
},
enter({ next }) {
return gsap.from(next.container, { opacity: 0 });
}
}---
Views
View objects provide page-specific logic based on namespace.
View Object Structure:
{
namespace: 'page-namespace',
beforeOnce() { /* ... */ },
afterOnce() { /* ... */ },
beforeLeave() { /* ... */ },
afterLeave() { /* ... */ },
beforeEnter() { /* ... */ },
afterEnter() { /* ... */ }
}Properties:
| Property | Type | Description |
|---|---|---|
namespace | string | Target namespace (matches data-barba-namespace) |
Examples:
barba.init({
views: [
{
namespace: 'home',
beforeEnter() {
console.log('About to enter home page');
},
afterEnter() {
// Initialize home-specific features
initHomeSlider();
initParallax();
},
beforeLeave() {
// Clean up
destroyHomeSlider();
}
},
{
namespace: 'product',
afterEnter({ next }) {
// Get product ID from URL
const productId = next.url.path.split('/').pop();
loadProduct(productId);
}
},
{
namespace: 'gallery',
afterEnter() {
initMasonry();
initLightbox();
},
beforeLeave() {
destroyMasonry();
destroyLightbox();
}
}
]
});---
Hooks
Complete reference for all 11 Barba.js hooks.
Hook Execution Order
Initial page load:
beforeOnce → once → afterOnceEvery navigation:
before → beforeLeave → leave → afterLeave →
beforeEnter → enter → afterEnter → afterHook Contexts
Hooks can be defined in three places:
1. Global hooks: Run on every transition
barba.hooks.before(() => { /* ... */ });2. Transition hooks: Run when that transition matches
barba.init({
transitions: [{
name: 'fade',
leave() { /* ... */ }
}]
});3. View hooks: Run for specific namespaces
barba.init({
views: [{
namespace: 'home',
afterEnter() { /* ... */ }
}]
});Async Hooks
Hooks can be synchronous or asynchronous:
// Synchronous
leave({ current }) {
current.container.style.opacity = 0;
}
// Promise
leave({ current }) {
return gsap.to(current.container, { opacity: 0 });
}
// Async/await
async leave({ current }) {
await gsap.to(current.container, { opacity: 0 });
}
// Manual async with this.async()
leave({ current }) {
const done = this.async();
setTimeout(() => {
gsap.to(current.container, { opacity: 0 });
done();
}, 500);
}Individual Hook Reference
beforeOnce
Runs once before initial page load (before once hook).
Available in: Transitions only When: Before first page render Use for: Initial setup, loading screens
{
beforeOnce() {
console.log('Before initial page render');
document.querySelector('.loader').classList.add('visible');
}
}once
Runs once on initial page load (animations for first view).
Available in: Transitions only When: During first page render Use for: Intro animations
{
async once({ next }) {
await gsap.from(next.container, {
opacity: 0,
y: 50,
duration: 1
});
}
}afterOnce
Runs once after initial page load (after once hook).
Available in: Transitions only When: After first page render completes Use for: Post-intro initialization
{
afterOnce() {
document.querySelector('.loader').classList.remove('visible');
console.log('Initial page loaded');
}
}before
Runs before every transition starts.
Available in: Transitions only When: Start of every navigation (except first load) Use for: Pre-transition setup, loading indicators
{
before() {
document.querySelector('.loader').classList.add('visible');
console.log('Transition starting');
}
}beforeLeave
Runs before leaving current page.
Available in: Transitions and Views When: Before leave animation Use for: Preparing leave animation, resetting scroll
{
beforeLeave({ current }) {
// Reset scroll
window.scrollTo(0, 0);
// Prepare elements
gsap.set(current.container.querySelectorAll('.fade'), { opacity: 1 });
}
}leave
Main hook for animating current page out.
Available in: Transitions only When: Current page exit animation Use for: Exit animations
{
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
x: -100,
duration: 0.5
});
}
}afterLeave
Runs after leaving current page.
Available in: Transitions and Views When: After leave animation completes Use for: Cleanup, removing event listeners
{
afterLeave({ current }) {
// Remove event listeners
current.container.querySelectorAll('button').forEach(btn => {
btn.removeEventListener('click', handleClick);
});
console.log('Left page:', current.namespace);
}
}beforeEnter
Runs before entering new page.
Available in: Transitions and Views When: Before enter animation (after new container inserted) Use for: Preparing enter animation, setting initial states
{
beforeEnter({ next }) {
// Set initial state for enter animation
gsap.set(next.container, { opacity: 0, x: 100 });
// Prepare page-specific elements
const images = next.container.querySelectorAll('img[data-src]');
images.forEach(img => {
img.src = img.dataset.src;
});
}
}enter
Main hook for animating new page in.
Available in: Transitions only When: New page entrance animation Use for: Entrance animations
{
async enter({ next }) {
await gsap.to(next.container, {
opacity: 1,
x: 0,
duration: 0.5
});
}
}afterEnter
Runs after entering new page.
Available in: Transitions and Views When: After enter animation completes Use for: Initializing page features, analytics
{
afterEnter({ next }) {
// Initialize page features
initSlider(next.container);
// Track page view
gtag('config', 'GA_ID', {
page_path: next.url.path
});
console.log('Entered page:', next.namespace);
}
}after
Runs after every transition completes.
Available in: Transitions only When: End of every navigation (except first load) Use for: Final cleanup, hiding loading indicators
{
after() {
document.querySelector('.loader').classList.remove('visible');
console.log('Transition complete');
}
}---
Data Object
All hooks receive a data object with information about current and next pages.
Data Object Structure:
{
current: {
container: HTMLElement,
html: string,
namespace: string,
url: { href, path, port, query }
},
next: {
container: HTMLElement,
html: string,
namespace: string,
url: { href, path, port, query }
},
trigger: string | HTMLElement,
event: Event
}Properties:
current
Information about the page being left.
| Property | Type | Description |
|---|---|---|
container | HTMLElement | Current page's data-barba="container" element |
html | string | Full HTML of current page |
namespace | string | Current page namespace |
url | object | URL information |
url.href | string | Full URL |
url.path | string | Path without query/hash |
url.port | string | Port number |
url.query | object | Query parameters as key-value pairs |
next
Information about the page being entered.
Same structure as current.
trigger
Source of the navigation.
| Value | Type | Description |
|---|---|---|
| Link element | HTMLElement | User clicked a link |
'barba' | string | Programmatic navigation via barba.go() |
'back' | string | Browser back button |
'forward' | string | Browser forward button |
event
The triggering event object (if navigation was from user interaction).
Usage Examples:
barba.hooks.before(({ current, next, trigger, event }) => {
console.log('Leaving:', current.namespace);
console.log('Entering:', next.namespace);
console.log('Current URL:', current.url.href);
console.log('Next URL:', next.url.href);
console.log('Trigger:', trigger);
// Check trigger type
if (trigger === 'back') {
console.log('User went back');
} else if (trigger instanceof HTMLElement) {
console.log('User clicked:', trigger.href);
}
// Access query parameters
if (next.url.query.id) {
console.log('Product ID:', next.url.query.id);
}
});---
Router Plugin
Package: @barba/router
Add route-based transition rules using path patterns.
Installation:
npm install --save-dev @barba/routerAPI:
import barbaRouter from '@barba/router';
barbaRouter.init({
routes: [
{ path: '/', name: 'home' },
{ path: '/about', name: 'about' },
{ path: '/products/:id', name: 'product' },
{ path: '/blog/:category/:slug', name: 'blog-post' },
{ path: '/:lang(en|fr)/:page', name: 'localized-page' }
]
});
barba.use(barbaRouter);Route Properties:
| Property | Type | Description |
|---|---|---|
path | string | URL pattern (supports path-to-regexp syntax) |
name | string | Route name (used in transition rules) |
Path Patterns:
'/products/:id' // Dynamic segment
'/blog/:category/:slug' // Multiple segments
'/:lang(en|fr)/:page' // Alternatives
'/files/:path*' // Wildcard (0 or more)
'/files/:path+' // Wildcard (1 or more)
'/products/:id?' // Optional segmentUsage in Transitions:
barba.init({
transitions: [{
name: 'product-transition',
to: { route: 'product' }, // Matches route name
enter({ next }) {
// Get route params from URL
const productId = next.url.path.split('/').pop();
console.log('Loading product:', productId);
}
}]
});---
Prefetch Plugin
Package: @barba/prefetch
Prefetch pages on link hover for faster navigation.
Installation:
npm install --save-dev @barba/prefetchAPI:
import barbaPrefetch from '@barba/prefetch';
barba.use(barbaPrefetch);
barba.init({
prefetch: {
root: null, // Element to observe (null = document)
timeout: 3000 // Cache timeout in ms
}
});How It Works:
1. User hovers over a link 2. Plugin fetches the page via AJAX 3. Page is cached 4. On click, cached page loads instantly (if still in cache)
Manual Prefetching:
// Prefetch specific URL
barba.prefetch('/about');
// Prefetch multiple URLs
['/about', '/contact', '/services'].forEach(url => {
barba.prefetch(url);
});---
CSS Plugin
Package: @barba/css
CSS-based transitions without writing JavaScript.
Installation:
npm install --save-dev @barba/cssUsage:
import barbaCSS from '@barba/css';
barba.use(barbaCSS);
barba.init();CSS Classes:
Barba adds classes during transitions:
| Class | When | Description |
|---|---|---|
.barba-once | Initial load | Applied to wrapper during once hook |
.barba-leave | Leave phase | Applied to current container |
.barba-leave-active | Leave active | Added when animation starts |
.barba-leave-to | Leave end | Added when animation should end |
.barba-enter | Enter phase | Applied to next container |
.barba-enter-active | Enter active | Added when animation starts |
.barba-enter-to | Enter end | Added when animation should end |
Example CSS:
/* Initial page load */
.barba-once [data-barba="container"] {
opacity: 0;
transform: translateY(50px);
transition: opacity 0.5s, transform 0.5s;
}
.barba-once.barba-once-active [data-barba="container"] {
opacity: 1;
transform: translateY(0);
}
/* Leave transition */
.barba-leave-active [data-barba="container"] {
opacity: 1;
transition: opacity 0.5s;
}
.barba-leave-to [data-barba="container"] {
opacity: 0;
}
/* Enter transition */
.barba-enter [data-barba="container"] {
opacity: 0;
}
.barba-enter-active [data-barba="container"] {
opacity: 1;
transition: opacity 0.5s;
}---
Head Plugin
Package: @barba/head
Automatically update <head> tags (title, meta) on navigation.
Installation:
npm install --save-dev @barba/headUsage:
import barbaHead from '@barba/head';
barba.use(barbaHead);
barba.init();What It Updates:
The plugin updates these <head> tags automatically:
<title><meta>(name, property, http-equiv)<link>(canonical, alternate)<script>(application/ld+json)
Manual Head Updates:
If not using the plugin, update manually:
barba.hooks.after(({ next }) => {
// Update title
document.title = next.html.match(/<title>(.*?)<\/title>/i)[1];
// Update meta description
const metaDesc = next.html.match(/<meta name="description" content="(.*?)"/i);
if (metaDesc) {
document.querySelector('meta[name="description"]').content = metaDesc[1];
}
});Barba.js + GSAP Integration Guide
Complete guide to using GSAP (Green Sock Animation Platform) with Barba.js for smooth, performant page transitions.
Table of Contents
- Why GSAP + Barba
- Setup
- Basic Integration Patterns
- Timeline-Based Transitions
- Advanced Patterns
- Performance Tips
- Common Issues
---
Why GSAP + Barba
Barba.js handles navigation and lifecycle management. GSAP handles the actual animations.
This combination provides:
- Performance: GSAP uses GPU acceleration and optimized rendering
- Control: Precise timing with timelines, stagger, and easing
- Simplicity: GSAP returns promises that Barba can await
- Power: Complex sequences, morphing, and advanced effects
---
Setup
Installation
# Install both libraries
npm install --save-dev @barba/core gsap
# Optional: GSAP plugins
npm install --save-dev gsap/ScrollTriggerBasic Import
import barba from '@barba/core';
import gsap from 'gsap';
// Optional GSAP plugins
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);---
Basic Integration Patterns
Pattern 1: Simple Fade
barba.init({
transitions: [{
name: 'fade',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.5,
ease: 'power2.inOut'
});
},
async enter({ next }) {
// Set initial state
gsap.set(next.container, { opacity: 0 });
// Animate in
await gsap.to(next.container, {
opacity: 1,
duration: 0.5,
ease: 'power2.inOut'
});
}
}]
});Key Points:
- GSAP's
to()returns a promise - Use
awaitto make Barba wait for animation completion gsap.set()sets initial state without animation
Pattern 2: Slide Transitions
{
name: 'slide',
sync: true, // Play leave and enter simultaneously
leave({ current }) {
return gsap.to(current.container, {
x: '-100%',
duration: 0.7,
ease: 'power3.inOut'
});
},
enter({ next }) {
// Start off-screen
gsap.set(next.container, { x: '100%' });
// Slide in
return gsap.to(next.container, {
x: '0%',
duration: 0.7,
ease: 'power3.inOut'
});
}
}Key Points:
sync: trueenables crossfade/overlap effect- Use
returninstead ofawait(same result) - GPU-accelerated properties (
x,y,opacity,scale)
Pattern 3: Scale and Fade
{
name: 'scale-fade',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
scale: 0.95,
duration: 0.5,
ease: 'power2.in'
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
// From
{
opacity: 0,
scale: 1.05
},
// To
{
opacity: 1,
scale: 1,
duration: 0.5,
ease: 'power2.out'
}
);
}
}Key Points:
fromTo()sets initial state and animates to final state- Different easings for enter/exit (asymmetric feel)
---
Timeline-Based Transitions
GSAP Timelines provide precise control over complex sequences.
Basic Timeline
{
name: 'timeline-fade',
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelector('h1'), {
y: -50,
opacity: 0,
duration: 0.3
})
.to(current.container.querySelector('.content'), {
y: -30,
opacity: 0,
duration: 0.3
}, '-=0.2') // Overlap by 0.2s
.to(current.container, {
opacity: 0,
duration: 0.2
});
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
// Set initial states
gsap.set(next.container, { opacity: 1 });
gsap.set(next.container.querySelector('h1'), { y: 50, opacity: 0 });
gsap.set(next.container.querySelector('.content'), { y: 30, opacity: 0 });
tl.to(next.container.querySelector('h1'), {
y: 0,
opacity: 1,
duration: 0.5,
ease: 'power3.out'
})
.to(next.container.querySelector('.content'), {
y: 0,
opacity: 1,
duration: 0.5,
ease: 'power3.out'
}, '-=0.3');
await tl.play();
}
}Timeline Position Parameter:
'-=0.2'- Start 0.2s before previous animation ends (overlap)'+=0.2'- Start 0.2s after previous animation ends (delay)'<'- Start at beginning of previous animation'>'- Start at end of previous animation
Staggered Elements
{
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelectorAll('.item'), {
y: -30,
opacity: 0,
duration: 0.4,
stagger: 0.05, // 0.05s delay between each item
ease: 'power2.in'
})
.to(current.container, {
opacity: 0,
duration: 0.3
});
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
gsap.set(next.container.querySelectorAll('.item'), { y: 30, opacity: 0 });
tl.to(next.container.querySelectorAll('.item'), {
y: 0,
opacity: 1,
duration: 0.5,
stagger: 0.05,
ease: 'power2.out'
});
await tl.play();
}
}Stagger Options:
stagger: {
amount: 0.5, // Total duration for all staggers
from: 'start', // 'start', 'end', 'center', or index number
grid: [5, 10], // For grid layouts [rows, columns]
axis: 'y', // 'x', 'y', or null
ease: 'power2.in' // Easing for stagger distribution
}Timeline with Labels
{
async leave({ current }) {
const tl = gsap.timeline();
// Add labels for reference
tl.addLabel('start')
.to(current.container.querySelector('.hero'), {
scale: 0.9,
opacity: 0,
duration: 0.5
})
.addLabel('hero-done')
.to(current.container.querySelector('.content'), {
y: -50,
opacity: 0,
duration: 0.4
}, 'hero-done-=0.2') // Start 0.2s before hero-done
.addLabel('content-done')
.to(current.container, {
opacity: 0,
duration: 0.2
}, 'content-done');
await tl.play();
}
}---
Advanced Patterns
Pattern 1: Conditional Animations
{
async leave({ current, next }) {
const isProduct = current.namespace === 'product' && next.namespace === 'product';
if (isProduct) {
// Fast transition between products
await gsap.to(current.container, {
opacity: 0,
scale: 0.95,
duration: 0.3
});
} else {
// Slower, more elaborate transition
const tl = gsap.timeline();
tl.to(current.container.querySelectorAll('.fade-item'), {
y: -30,
opacity: 0,
duration: 0.4,
stagger: 0.05
})
.to(current.container, {
opacity: 0,
duration: 0.3
});
await tl.play();
}
}
}Pattern 2: Direction-Based Transitions
{
async leave({ current }) {
const direction = barba.history.direction;
const isBack = direction === 'back';
await gsap.to(current.container, {
x: isBack ? '100%' : '-100%', // Slide right if going back
duration: 0.6,
ease: 'power2.inOut'
});
},
async enter({ next }) {
const direction = barba.history.direction;
const isBack = direction === 'back';
gsap.set(next.container, {
x: isBack ? '-100%' : '100%' // Come from left if going back
});
await gsap.to(next.container, {
x: '0%',
duration: 0.6,
ease: 'power2.inOut'
});
}
}Pattern 3: Curtain Effect
{
async leave({ current }) {
// Animate curtain down
const curtain = document.querySelector('.transition-curtain');
await gsap.fromTo(curtain,
{ yPercent: -100 },
{
yPercent: 0,
duration: 0.6,
ease: 'power2.inOut'
}
);
},
async enter({ next }) {
// Animate curtain up
const curtain = document.querySelector('.transition-curtain');
await gsap.to(curtain, {
yPercent: 100,
duration: 0.6,
ease: 'power2.inOut'
});
}
}HTML for Curtain:
<div class="transition-curtain"></div>CSS for Curtain:
.transition-curtain {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
z-index: 9999;
pointer-events: none;
transform: translateY(-100%);
}Pattern 4: Morphing Transitions
import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin';
gsap.registerPlugin(MorphSVGPlugin);
{
async leave({ current }) {
const shape = document.querySelector('.transition-shape path');
await gsap.to(shape, {
morphSVG: '.shape-expanded',
duration: 0.8,
ease: 'power2.inOut'
});
},
async enter({ next }) {
const shape = document.querySelector('.transition-shape path');
await gsap.to(shape, {
morphSVG: '.shape-collapsed',
duration: 0.8,
ease: 'power2.inOut'
});
}
}Pattern 5: Split Text Animations
import { SplitText } from 'gsap/SplitText';
gsap.registerPlugin(SplitText);
{
async enter({ next }) {
const title = next.container.querySelector('h1');
const split = new SplitText(title, { type: 'chars,words' });
await gsap.from(split.chars, {
opacity: 0,
y: 50,
rotationX: -90,
stagger: 0.02,
duration: 0.8,
ease: 'back.out(1.7)'
});
// Clean up
split.revert();
}
}Pattern 6: Parallax Layers
{
sync: true,
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelector('.layer-1'), {
y: -100,
opacity: 0,
duration: 0.8
}, 0)
.to(current.container.querySelector('.layer-2'), {
y: -50,
opacity: 0,
duration: 0.8
}, 0)
.to(current.container.querySelector('.layer-3'), {
y: -25,
opacity: 0,
duration: 0.8
}, 0);
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
gsap.set(next.container.querySelectorAll('[class^="layer-"]'), {
opacity: 0
});
tl.from(next.container.querySelector('.layer-1'), {
y: 100,
opacity: 0,
duration: 0.8
}, 0)
.from(next.container.querySelector('.layer-2'), {
y: 50,
opacity: 0,
duration: 0.8
}, 0)
.from(next.container.querySelector('.layer-3'), {
y: 25,
opacity: 0,
duration: 0.8
}, 0);
await tl.play();
}
}---
Performance Tips
1. Use GPU-Accelerated Properties
// ✅ Good - GPU accelerated
gsap.to(element, {
x: 100,
y: 50,
opacity: 0.5,
scale: 1.2,
rotation: 45
});
// ❌ Avoid - causes reflow/repaint
gsap.to(element, {
left: '100px',
top: '50px',
width: '200px',
height: '300px'
});2. Use will-change with Caution
/* Add to elements that will animate */
[data-barba="container"] {
will-change: transform, opacity;
}
/* Remove after transition */barba.hooks.after(() => {
// Remove will-change after transition
document.querySelectorAll('[data-barba="container"]').forEach(el => {
el.style.willChange = 'auto';
});
});3. Kill Running Animations
{
beforeLeave({ current }) {
// Kill any running animations on current page
gsap.killTweensOf(current.container.querySelectorAll('*'));
}
}4. Batch DOM Queries
// ❌ Slow - queries DOM multiple times
{
async leave({ current }) {
await gsap.to(current.container.querySelector('h1'), { opacity: 0 });
await gsap.to(current.container.querySelector('.content'), { opacity: 0 });
await gsap.to(current.container.querySelector('.footer'), { opacity: 0 });
}
}
// ✅ Fast - queries DOM once
{
async leave({ current }) {
const h1 = current.container.querySelector('h1');
const content = current.container.querySelector('.content');
const footer = current.container.querySelector('.footer');
const tl = gsap.timeline();
tl.to(h1, { opacity: 0 })
.to(content, { opacity: 0 }, '<')
.to(footer, { opacity: 0 }, '<');
await tl.play();
}
}5. Use force3D for Mobile
gsap.to(element, {
x: 100,
force3D: true, // Force GPU acceleration
duration: 0.5
});---
Common Issues
Issue 1: Animation Doesn't Wait
Problem: Page transitions instantly without animation.
Solution: Always return promise or use await:
// ❌ Wrong
leave({ current }) {
gsap.to(current.container, { opacity: 0 });
}
// ✅ Correct
leave({ current }) {
return gsap.to(current.container, { opacity: 0 });
}
// ✅ Also correct
async leave({ current }) {
await gsap.to(current.container, { opacity: 0 });
}Issue 2: Flash of Content (FOUC)
Problem: New page visible before enter animation.
Solution: Set initial state in beforeEnter or CSS:
beforeEnter({ next }) {
gsap.set(next.container, { opacity: 0 });
}Or in CSS:
[data-barba="container"] {
opacity: 0;
}Issue 3: Sync Mode Layout Shift
Problem: Containers stack during sync transitions.
Solution: Position absolutely during transition:
[data-barba="wrapper"] {
position: relative;
}
[data-barba="container"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
}Or in JavaScript:
{
sync: true,
beforeLeave({ current }) {
gsap.set(current.container, {
position: 'absolute',
top: 0,
width: '100%'
});
}
}Issue 4: ScrollTrigger Conflicts
Problem: ScrollTrigger instances persist after page change.
Solution: Kill ScrollTriggers in beforeLeave:
import { ScrollTrigger } from 'gsap/ScrollTrigger';
barba.hooks.beforeLeave(() => {
// Kill all ScrollTrigger instances
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
});
// Or use scoped instances
barba.init({
views: [{
namespace: 'home',
afterEnter() {
// Create ScrollTriggers
this.scrollTriggers = [];
this.scrollTriggers.push(
ScrollTrigger.create({
trigger: '.section',
// ...
})
);
},
beforeLeave() {
// Kill scoped ScrollTriggers
this.scrollTriggers.forEach(trigger => trigger.kill());
}
}]
});Issue 5: Memory Leaks
Problem: Timelines/tweens accumulate.
Solution: Kill tweens before creating new ones:
barba.hooks.beforeLeave(({ current }) => {
// Kill all tweens on current page
gsap.killTweensOf(current.container);
gsap.killTweensOf(current.container.querySelectorAll('*'));
});Issue 6: Timeline Doesn't Await
Problem: Timeline starts but doesn't wait for completion.
Solution: Call .play() on timeline (it returns a promise):
// ❌ Wrong
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container, { opacity: 0 });
// Timeline starts but function returns immediately
}
// ✅ Correct
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container, { opacity: 0 });
await tl.play(); // Wait for timeline to complete
}---
Easing Reference
Common GSAP easings for transitions:
// Smooth and natural
ease: 'power2.inOut'
// Gentle acceleration
ease: 'power1.out'
// Strong deceleration
ease: 'power3.out'
// Bounce effect
ease: 'back.out(1.7)'
// Elastic effect
ease: 'elastic.out(1, 0.3)'
// Custom bezier
ease: 'cubic-bezier(0.4, 0, 0.2, 1)'Recommended for Barba transitions:
- Leave:
'power2.in'or'power2.inOut' - Enter:
'power2.out'or'power3.out'
Barba.js Hooks Guide
Comprehensive guide to all 11 Barba.js lifecycle hooks with execution order, use cases, and examples.
Table of Contents
- Hook Execution Order
- Hook Types and Contexts
- Async Hook Patterns
- Individual Hooks
- Common Hook Patterns
- Best Practices
---
Hook Execution Order
Initial Page Load
When the website first loads:
beforeOnce → once → afterOnceTimeline: 1. beforeOnce - Setup before first render 2. once - Intro animation plays 3. afterOnce - Cleanup after first render
Every Navigation
When navigating between pages:
before → beforeLeave → leave → afterLeave →
beforeEnter → enter → afterEnter → afterTimeline: 1. before - Transition starts 2. beforeLeave - Prepare to leave current page 3. leave - Animate current page out 4. afterLeave - Cleanup after leaving 5. [Container swap happens here] 6. beforeEnter - Prepare to enter new page 7. enter - Animate new page in 8. afterEnter - Initialize new page 9. after - Transition complete
Sync Mode Execution Order
With sync: true, the order changes:
before → beforeLeave → beforeEnter →
(leave + enter simultaneously) →
afterLeave → afterEnter → afterKey Difference: leave and enter run at the same time (crossfade effect).
---
Hook Types and Contexts
1. Global Hooks
Run on every transition, registered via barba.hooks:
barba.hooks.before(() => {
console.log('Every transition');
});
barba.hooks.afterEnter(({ next }) => {
console.log('Entered:', next.namespace);
});Use for:
- Universal behavior (analytics, scroll reset)
- Debug logging
- Loading indicators
- Third-party script re-initialization
2. Transition Hooks
Run when a specific transition matches:
barba.init({
transitions: [{
name: 'fade',
leave({ current }) {
return gsap.to(current.container, { opacity: 0 });
},
enter({ next }) {
return gsap.from(next.container, { opacity: 0 });
}
}]
});Use for:
- Transition-specific animations
- Conditional behaviors based on navigation context
3. View Hooks
Run for specific namespaces:
barba.init({
views: [{
namespace: 'home',
afterEnter() {
console.log('Home page entered');
initHomeFeatures();
},
beforeLeave() {
console.log('Leaving home page');
cleanupHomeFeatures();
}
}]
});Use for:
- Page-specific initialization
- Feature cleanup before leaving page
Hook Availability Matrix
| Hook | Global | Transition | View |
|---|---|---|---|
beforeOnce | ✅ | ✅ | ❌ |
once | ✅ | ✅ | ❌ |
afterOnce | ✅ | ✅ | ❌ |
before | ✅ | ✅ | ❌ |
beforeLeave | ✅ | ✅ | ✅ |
leave | ✅ | ✅ | ❌ |
afterLeave | ✅ | ✅ | ✅ |
beforeEnter | ✅ | ✅ | ✅ |
enter | ✅ | ✅ | ❌ |
afterEnter | ✅ | ✅ | ✅ |
after | ✅ | ✅ | ❌ |
---
Async Hook Patterns
Hooks can be synchronous or asynchronous. Barba waits for async hooks to complete before continuing.
Pattern 1: Returning Promises
leave({ current }) {
// Return the promise
return gsap.to(current.container, {
opacity: 0,
duration: 0.5
});
}Pattern 2: Async/Await
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.5
});
// Can chain multiple awaits
await someOtherAsyncOperation();
}Pattern 3: Manual async() Method
leave({ current }) {
const done = this.async();
setTimeout(() => {
gsap.to(current.container, { opacity: 0 });
done(); // Signal completion
}, 500);
}Pattern 4: Promise.all for Parallel Operations
async leave({ current }) {
// Run animations in parallel
await Promise.all([
gsap.to(current.container.querySelector('h1'), { opacity: 0 }),
gsap.to(current.container.querySelector('.content'), { y: -50 }),
fetch('/api/track-exit')
]);
}---
Individual Hooks
beforeOnce
Execution: Before initial page load Available in: Global, Transition Async: Yes
Purpose: Setup before the first page renders.
Common Use Cases:
- Show loading screen
- Prepare intro animation
- Initial app setup
Examples:
// Show loader
barba.hooks.beforeOnce(() => {
document.querySelector('.loader').style.display = 'flex';
});
// In transition
{
beforeOnce() {
gsap.set('.intro-title', { opacity: 0, y: 50 });
}
}---
once
Execution: During initial page load Available in: Global, Transition Async: Yes
Purpose: Animate the first page view (intro animation).
Common Use Cases:
- Intro animations
- Reveal content on first load
- Splash screen effects
Examples:
// Simple fade in
{
async once({ next }) {
await gsap.from(next.container, {
opacity: 0,
duration: 1
});
}
}
// Complex intro sequence
{
async once({ next }) {
const tl = gsap.timeline();
tl.to('.loader', { opacity: 0, duration: 0.5 })
.set('.loader', { display: 'none' })
.from('.intro-title', { opacity: 0, y: 50, duration: 0.8 })
.from('.intro-subtitle', { opacity: 0, y: 30, duration: 0.6 }, '-=0.4')
.from(next.container, { opacity: 0, duration: 0.5 });
await tl.play();
}
}---
afterOnce
Execution: After initial page load Available in: Global, Transition Async: Yes
Purpose: Cleanup or initialization after intro animation.
Common Use Cases:
- Hide loading screen
- Initialize page features
- Track page load
Examples:
barba.hooks.afterOnce(() => {
// Hide loader
document.querySelector('.loader').style.display = 'none';
// Track page load
gtag('event', 'page_load', { page: window.location.pathname });
});---
before
Execution: Before every transition starts Available in: Global, Transition Async: Yes
Purpose: Setup before transition begins.
Common Use Cases:
- Show loading indicator
- Prepare global state
- Disable interactions during transition
Examples:
// Global loading indicator
barba.hooks.before(() => {
document.body.classList.add('is-transitioning');
document.querySelector('.page-loader').classList.add('active');
});
// Disable scroll during transition
barba.hooks.before(() => {
document.body.style.overflow = 'hidden';
});---
beforeLeave
Execution: Before leaving current page Available in: Global, Transition, View Async: Yes
Purpose: Prepare current page before leave animation.
Common Use Cases:
- Reset scroll position
- Prepare elements for leave animation
- Save page state
Examples:
// Reset scroll (global)
barba.hooks.beforeLeave(() => {
window.scrollTo(0, 0);
});
// Prepare animation elements
{
beforeLeave({ current }) {
// Reset any transforms that might interfere
gsap.set(current.container.querySelectorAll('.animated'), {
clearProps: 'all'
});
}
}
// View-specific cleanup
{
views: [{
namespace: 'gallery',
beforeLeave() {
// Save scroll position for this page
sessionStorage.setItem('galleryScroll', window.scrollY);
}
}]
}---
leave
Execution: Current page exit animation Available in: Global, Transition Async: Yes (must return promise or use async/await)
Purpose: Animate current page out.
Common Use Cases:
- Fade out animation
- Slide out animation
- Complex exit sequences
Examples:
// Simple fade out
{
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.5
});
}
}
// Slide out left
{
leave({ current }) {
return gsap.to(current.container, {
x: '-100%',
duration: 0.6,
ease: 'power2.inOut'
});
}
}
// Complex staggered exit
{
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelector('h1'), {
y: -50,
opacity: 0,
duration: 0.3
})
.to(current.container.querySelectorAll('.content > *'), {
y: -30,
opacity: 0,
duration: 0.3,
stagger: 0.05
}, '-=0.2')
.to(current.container, {
opacity: 0,
duration: 0.2
});
await tl.play();
}
}---
afterLeave
Execution: After leaving current page Available in: Global, Transition, View Async: Yes
Purpose: Cleanup after current page has animated out.
Common Use Cases:
- Remove event listeners
- Clean up third-party widgets
- Debug logging
Examples:
// Global cleanup
barba.hooks.afterLeave(({ current }) => {
console.log('Left page:', current.namespace);
});
// View-specific cleanup
{
views: [{
namespace: 'video-gallery',
afterLeave({ current }) {
// Pause all videos
current.container.querySelectorAll('video').forEach(video => {
video.pause();
video.currentTime = 0;
});
// Remove event listeners
current.container.querySelectorAll('.video-play').forEach(btn => {
btn.removeEventListener('click', handleVideoPlay);
});
}
}]
}---
beforeEnter
Execution: Before entering new page Available in: Global, Transition, View Async: Yes
Purpose: Prepare new page before enter animation.
Common Use Cases:
- Set initial animation states
- Load images
- Prepare page-specific features
Examples:
// Set initial state for animation
{
beforeEnter({ next }) {
gsap.set(next.container, { opacity: 0 });
}
}
// Load images
barba.hooks.beforeEnter(({ next }) => {
const images = next.container.querySelectorAll('img[data-src]');
images.forEach(img => {
img.src = img.dataset.src;
img.removeAttribute('data-src');
});
});
// Restore scroll position
{
views: [{
namespace: 'gallery',
beforeEnter() {
const savedScroll = sessionStorage.getItem('galleryScroll');
if (savedScroll) {
window.scrollTo(0, parseInt(savedScroll));
}
}
}]
}---
enter
Execution: New page entrance animation Available in: Global, Transition Async: Yes (must return promise or use async/await)
Purpose: Animate new page in.
Common Use Cases:
- Fade in animation
- Slide in animation
- Complex entrance sequences
Examples:
// Simple fade in
{
async enter({ next }) {
await gsap.from(next.container, {
opacity: 0,
duration: 0.5
});
}
}
// Slide in from right
{
enter({ next }) {
return gsap.from(next.container, {
x: '100%',
duration: 0.6,
ease: 'power2.inOut'
});
}
}
// Complex staggered entrance
{
async enter({ next }) {
const tl = gsap.timeline();
// Set initial states
gsap.set(next.container, { opacity: 1 });
gsap.set(next.container.querySelector('h1'), { y: 50, opacity: 0 });
gsap.set(next.container.querySelectorAll('.content > *'), { y: 30, opacity: 0 });
tl.to(next.container.querySelector('h1'), {
y: 0,
opacity: 1,
duration: 0.5,
ease: 'power3.out'
})
.to(next.container.querySelectorAll('.content > *'), {
y: 0,
opacity: 1,
duration: 0.5,
stagger: 0.05,
ease: 'power2.out'
}, '-=0.3');
await tl.play();
}
}---
afterEnter
Execution: After entering new page Available in: Global, Transition, View Async: Yes
Purpose: Initialize features after new page has animated in.
Common Use Cases:
- Initialize page features
- Track page views
- Start auto-playing content
Examples:
// Global analytics tracking
barba.hooks.afterEnter(({ next }) => {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: next.url.path,
page_title: document.title
});
});
// Re-initialize third-party scripts
barba.hooks.afterEnter(() => {
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
if (typeof twttr !== 'undefined') {
twttr.widgets.load();
}
});
// View-specific initialization
{
views: [
{
namespace: 'home',
afterEnter() {
initHomeSlider();
initParallaxEffects();
}
},
{
namespace: 'product',
afterEnter({ next }) {
const productId = next.url.path.split('/').pop();
loadProductData(productId);
}
},
{
namespace: 'video',
afterEnter({ next }) {
const video = next.container.querySelector('video');
if (video) {
video.play();
}
}
}
]
}---
after
Execution: After every transition completes Available in: Global, Transition Async: Yes
Purpose: Final cleanup after transition.
Common Use Cases:
- Hide loading indicators
- Re-enable interactions
- Debug logging
Examples:
// Global loading indicator
barba.hooks.after(() => {
document.body.classList.remove('is-transitioning');
document.querySelector('.page-loader').classList.remove('active');
});
// Re-enable scroll
barba.hooks.after(() => {
document.body.style.overflow = '';
});
// Debug transition
barba.hooks.after(({ current, next }) => {
console.log(`Transition complete: ${current.namespace} → ${next.namespace}`);
});---
Common Hook Patterns
Pattern 1: Loading Indicator
barba.hooks.before(() => {
gsap.to('.loader', { opacity: 1, duration: 0.3 });
});
barba.hooks.after(() => {
gsap.to('.loader', { opacity: 0, duration: 0.3 });
});Pattern 2: Scroll Management
barba.hooks.beforeLeave(() => {
// Save current scroll position
sessionStorage.setItem('scrollPos', window.scrollY);
});
barba.hooks.beforeEnter(({ next }) => {
// Reset to top for new pages
window.scrollTo(0, 0);
// Or restore scroll for same page
// const savedScroll = sessionStorage.getItem('scrollPos');
// if (savedScroll) window.scrollTo(0, parseInt(savedScroll));
});Pattern 3: Analytics Tracking
barba.hooks.after(({ next }) => {
// Google Analytics 4
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: next.url.path,
page_title: document.title
});
// Or GTM data layer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'pageview',
page: next.url.path
});
});Pattern 4: Third-Party Script Re-Init
barba.hooks.afterEnter(() => {
// Syntax highlighting
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
// Social widgets
if (typeof twttr !== 'undefined') {
twttr.widgets.load();
}
if (typeof FB !== 'undefined') {
FB.XFBML.parse();
}
// Custom form library
if (typeof customForms !== 'undefined') {
customForms.init();
}
});Pattern 5: View-Specific Features
barba.init({
views: [
{
namespace: 'home',
afterEnter() {
initHomeSlider();
initParallax();
},
beforeLeave() {
destroyHomeSlider();
}
},
{
namespace: 'shop',
afterEnter() {
initProductFilters();
initAddToCart();
},
beforeLeave() {
// Cleanup
document.querySelectorAll('.product-quick-view').forEach(el => {
el.remove();
});
}
}
]
});Pattern 6: Conditional Transitions
barba.init({
transitions: [
// Slow fade for first visit
{
name: 'first-visit',
once: async ({ next }) => {
await gsap.from(next.container, {
opacity: 0,
duration: 1.5
});
}
},
// Fast fade for same namespace
{
name: 'same-page-type',
custom: ({ current, next }) => current.namespace === next.namespace,
leave: ({ current }) => {
return gsap.to(current.container, { opacity: 0, duration: 0.2 });
},
enter: ({ next }) => {
return gsap.from(next.container, { opacity: 0, duration: 0.2 });
}
},
// Slower transition for different page types
{
name: 'different-page-type',
leave: ({ current }) => {
return gsap.to(current.container, { opacity: 0, duration: 0.5 });
},
enter: ({ next }) => {
return gsap.from(next.container, { opacity: 0, duration: 0.5 });
}
}
]
});---
Best Practices
1. Always Return Promises or Use Async/Await
// ❌ Wrong - animation won't wait
leave({ current }) {
gsap.to(current.container, { opacity: 0 });
}
// ✅ Correct - returns promise
leave({ current }) {
return gsap.to(current.container, { opacity: 0 });
}
// ✅ Also correct - async/await
async leave({ current }) {
await gsap.to(current.container, { opacity: 0 });
}2. Use Global Hooks for Universal Behavior
// ✅ Good - analytics tracking applies everywhere
barba.hooks.after(({ next }) => {
gtag('config', 'GA_ID', { page_path: next.url.path });
});
// ❌ Avoid - repeating in every transition
{
transitions: [{
after({ next }) {
gtag('config', 'GA_ID', { page_path: next.url.path });
}
}]
}3. Use View Hooks for Page-Specific Logic
// ✅ Good - isolated to home page
{
views: [{
namespace: 'home',
afterEnter() {
initHomeSlider();
}
}]
}
// ❌ Avoid - checking namespace manually
barba.hooks.afterEnter(({ next }) => {
if (next.namespace === 'home') {
initHomeSlider();
}
});4. Clean Up in beforeLeave, Not afterLeave
// ✅ Good - cleanup before leaving
{
views: [{
namespace: 'video',
beforeLeave({ current }) {
current.container.querySelectorAll('video').forEach(v => v.pause());
}
}]
}
// ❌ Risky - container might already be removed
{
views: [{
namespace: 'video',
afterLeave({ current }) {
// current.container might not be in DOM anymore
current.container.querySelectorAll('video').forEach(v => v.pause());
}
}]
}5. Set Initial States in beforeEnter, Not enter
// ✅ Good - prevents flash of content
{
beforeEnter({ next }) {
gsap.set(next.container, { opacity: 0 });
},
enter({ next }) {
return gsap.to(next.container, { opacity: 1 });
}
}
// ❌ Risky - might flash visible
{
enter({ next }) {
return gsap.from(next.container, { opacity: 0 });
}
}6. Use Hook Contexts Appropriately
// ✅ Correct usage
barba.init({
// Global hooks - universal behavior
hooks: {
after: () => { /* ... */ }
},
// Transitions - animation logic
transitions: [{
leave() { /* ... */ },
enter() { /* ... */ }
}],
// Views - page-specific logic
views: [{
namespace: 'home',
afterEnter() { /* ... */ }
}]
});7. Handle Errors Gracefully
barba.hooks.afterEnter(({ next }) => {
try {
initPageFeatures(next.container);
} catch (error) {
console.error('Failed to initialize page features:', error);
// Fallback or report error
}
});Barba.js Transition Patterns Library
Copy-paste transition implementations for common page transition effects.
Table of Contents
- Fade Transitions
- Slide Transitions
- Scale Transitions
- Creative Transitions
- Conditional Transitions
- Production Examples
---
Fade Transitions
Simple Fade
Classic fade out → fade in:
{
name: 'fade',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.5,
ease: 'power2.inOut'
});
},
async enter({ next }) {
gsap.set(next.container, { opacity: 0 });
await gsap.to(next.container, {
opacity: 1,
duration: 0.5,
ease: 'power2.inOut'
});
}
}Crossfade (Sync)
Both pages fade simultaneously:
{
name: 'crossfade',
sync: true,
leave({ current }) {
return gsap.to(current.container, {
opacity: 0,
duration: 0.8,
ease: 'power2.inOut'
});
},
enter({ next }) {
return gsap.from(next.container, {
opacity: 0,
duration: 0.8,
ease: 'power2.inOut'
});
}
}Fade with Scale
Fade + subtle zoom:
{
name: 'fade-scale',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
scale: 0.95,
duration: 0.5,
ease: 'power2.in'
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
{
opacity: 0,
scale: 1.05
},
{
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'power2.out'
}
);
}
}---
Slide Transitions
Horizontal Slide
Old page slides left, new page slides in from right:
{
name: 'slide-horizontal',
sync: true,
leave({ current }) {
return gsap.to(current.container, {
x: '-100%',
duration: 0.7,
ease: 'power3.inOut'
});
},
enter({ next }) {
gsap.set(next.container, { x: '100%' });
return gsap.to(next.container, {
x: '0%',
duration: 0.7,
ease: 'power3.inOut'
});
}
}Vertical Slide
Old page slides up, new page slides in from bottom:
{
name: 'slide-vertical',
sync: true,
leave({ current }) {
return gsap.to(current.container, {
y: '-100%',
duration: 0.7,
ease: 'power3.inOut'
});
},
enter({ next }) {
gsap.set(next.container, { y: '100%' });
return gsap.to(next.container, {
y: '0%',
duration: 0.7,
ease: 'power3.inOut'
});
}
}Direction-Based Slide
Slide direction based on browser history:
{
name: 'slide-smart',
sync: true,
leave({ current }) {
const direction = barba.history.direction;
const isBack = direction === 'back';
return gsap.to(current.container, {
x: isBack ? '100%' : '-100%',
duration: 0.6,
ease: 'power2.inOut'
});
},
enter({ next }) {
const direction = barba.history.direction;
const isBack = direction === 'back';
gsap.set(next.container, {
x: isBack ? '-100%' : '100%'
});
return gsap.to(next.container, {
x: '0%',
duration: 0.6,
ease: 'power2.inOut'
});
}
}CSS Required (for sync mode):
[data-barba="wrapper"] {
position: relative;
overflow: hidden;
}
[data-barba="container"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
}---
Scale Transitions
Zoom Out → Zoom In
{
name: 'zoom',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
scale: 0.8,
duration: 0.5,
ease: 'power2.in'
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
{
opacity: 0,
scale: 1.2
},
{
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'power2.out'
}
);
}
}Rotate Zoom
3D rotation with zoom:
{
name: 'rotate-zoom',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
scale: 0.9,
rotationY: 15,
duration: 0.6,
ease: 'power2.in'
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
{
opacity: 0,
scale: 0.9,
rotationY: -15
},
{
opacity: 1,
scale: 1,
rotationY: 0,
duration: 0.7,
ease: 'power2.out'
}
);
}
}CSS Required:
[data-barba="wrapper"] {
perspective: 1000px;
}---
Creative Transitions
Curtain Effect
Animated overlay curtain:
{
name: 'curtain',
async leave({ current }) {
const curtain = document.querySelector('.transition-curtain');
// Bring curtain down
await gsap.fromTo(curtain,
{ yPercent: -100 },
{
yPercent: 0,
duration: 0.6,
ease: 'power2.inOut'
}
);
},
async enter({ next }) {
const curtain = document.querySelector('.transition-curtain');
// Lift curtain up
await gsap.to(curtain, {
yPercent: 100,
duration: 0.6,
ease: 'power2.inOut'
});
}
}HTML Required:
<div class="transition-curtain"></div>CSS Required:
.transition-curtain {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
z-index: 9999;
pointer-events: none;
transform: translateY(-100%);
}Wipe Effect
Diagonal wipe transition:
{
name: 'wipe',
async leave({ current }) {
const wipe = document.querySelector('.transition-wipe');
await gsap.fromTo(wipe,
{
xPercent: -100,
skewX: -10
},
{
xPercent: 0,
skewX: 0,
duration: 0.8,
ease: 'power2.inOut'
}
);
},
async enter({ next }) {
const wipe = document.querySelector('.transition-wipe');
gsap.set(next.container, { opacity: 1 });
await gsap.to(wipe, {
xPercent: 100,
skewX: 10,
duration: 0.8,
ease: 'power2.inOut'
});
}
}HTML Required:
<div class="transition-wipe"></div>CSS Required:
.transition-wipe {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
z-index: 9999;
pointer-events: none;
transform: translateX(-100%);
}Staggered Elements
Elements animate out/in individually:
{
name: 'stagger',
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelectorAll('.stagger-item'), {
y: -50,
opacity: 0,
duration: 0.5,
stagger: 0.05,
ease: 'power2.in'
})
.to(current.container, {
opacity: 0,
duration: 0.3
}, '-=0.2');
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
gsap.set(next.container.querySelectorAll('.stagger-item'), {
y: 50,
opacity: 0
});
tl.to(next.container.querySelectorAll('.stagger-item'), {
y: 0,
opacity: 1,
duration: 0.6,
stagger: 0.05,
ease: 'power2.out'
});
await tl.play();
}
}HTML Classes Required:
<div data-barba="container">
<h1 class="stagger-item">Title</h1>
<p class="stagger-item">Paragraph 1</p>
<p class="stagger-item">Paragraph 2</p>
<div class="stagger-item">Content</div>
</div>---
Conditional Transitions
Different Transition Per Namespace
barba.init({
transitions: [
// From home: fade
{
name: 'from-home',
from: { namespace: 'home' },
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.5
});
},
async enter({ next }) {
await gsap.from(next.container, {
opacity: 0,
duration: 0.5
});
}
},
// To/from product: slide
{
name: 'product-transition',
from: { namespace: 'product' },
to: { namespace: 'product' },
sync: true,
leave({ current }) {
return gsap.to(current.container, {
x: '-100%',
duration: 0.6
});
},
enter({ next }) {
gsap.set(next.container, { x: '100%' });
return gsap.to(next.container, {
x: '0%',
duration: 0.6
});
}
},
// Default: crossfade
{
name: 'default',
sync: true,
leave({ current }) {
return gsap.to(current.container, {
opacity: 0,
duration: 0.4
});
},
enter({ next }) {
return gsap.from(next.container, {
opacity: 0,
duration: 0.4
});
}
}
]
});Custom Rule Based on Data Attribute
{
name: 'article-to-article',
custom: ({ current, next }) => {
// Check if both pages have data-article attribute
return current.container.dataset.article && next.container.dataset.article;
},
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
scale: 0.95,
duration: 0.3
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
{ opacity: 0, scale: 1.05 },
{ opacity: 1, scale: 1, duration: 0.4 }
);
}
}---
Production Examples
E-commerce Product Pages
Fast transitions between products, slower elsewhere:
barba.init({
transitions: [
// Product to product: fast slide
{
name: 'product-to-product',
from: { namespace: 'product' },
to: { namespace: 'product' },
sync: true,
leave({ current }) {
return gsap.to(current.container, {
x: '-50%',
opacity: 0,
duration: 0.4,
ease: 'power2.inOut'
});
},
enter({ next }) {
gsap.set(next.container, { x: '50%', opacity: 0 });
return gsap.to(next.container, {
x: '0%',
opacity: 1,
duration: 0.4,
ease: 'power2.inOut'
});
}
},
// Default: elegant fade
{
name: 'default',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
y: -30,
duration: 0.6,
ease: 'power2.in'
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
{ opacity: 0, y: 30 },
{ opacity: 1, y: 0, duration: 0.7, ease: 'power2.out' }
);
}
}
]
});Portfolio with Loading Indicator
barba.init({
transitions: [{
name: 'portfolio',
async leave({ current }) {
const loader = document.querySelector('.page-loader');
// Fade out content
await gsap.to(current.container, {
opacity: 0,
duration: 0.4
});
// Show loader
gsap.to(loader, {
opacity: 1,
duration: 0.3
});
},
async enter({ next }) {
const loader = document.querySelector('.page-loader');
// Hide loader
await gsap.to(loader, {
opacity: 0,
duration: 0.3
});
// Fade in content
await gsap.from(next.container, {
opacity: 0,
duration: 0.5
});
}
}]
});HTML:
<div class="page-loader">Loading...</div>CSS:
.page-loader {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.5rem;
opacity: 0;
z-index: 9998;
pointer-events: none;
}Blog with Staggered Content
barba.init({
transitions: [{
name: 'blog',
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelector('.post-header'), {
y: -50,
opacity: 0,
duration: 0.4
})
.to(current.container.querySelectorAll('.post-content > *'), {
y: -30,
opacity: 0,
duration: 0.3,
stagger: 0.03
}, '-=0.3')
.to(current.container, {
opacity: 0,
duration: 0.2
});
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
gsap.set(next.container.querySelector('.post-header'), {
y: 50,
opacity: 0
});
gsap.set(next.container.querySelectorAll('.post-content > *'), {
y: 30,
opacity: 0
});
tl.to(next.container.querySelector('.post-header'), {
y: 0,
opacity: 1,
duration: 0.6,
ease: 'power3.out'
})
.to(next.container.querySelectorAll('.post-content > *'), {
y: 0,
opacity: 1,
duration: 0.5,
stagger: 0.05,
ease: 'power2.out'
}, '-=0.4');
await tl.play();
}
}]
});Agency Site with Parallax Layers
barba.init({
transitions: [{
name: 'parallax',
sync: true,
async leave({ current }) {
const tl = gsap.timeline();
// Parallax effect - different speeds
tl.to(current.container.querySelector('.layer-bg'), {
y: -50,
opacity: 0,
duration: 0.8
}, 0)
.to(current.container.querySelector('.layer-mid'), {
y: -100,
opacity: 0,
duration: 0.8
}, 0)
.to(current.container.querySelector('.layer-front'), {
y: -150,
opacity: 0,
duration: 0.8
}, 0);
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
gsap.set(next.container.querySelectorAll('[class^="layer-"]'), {
opacity: 0
});
tl.from(next.container.querySelector('.layer-bg'), {
y: 50,
opacity: 0,
duration: 0.8
}, 0)
.from(next.container.querySelector('.layer-mid'), {
y: 100,
opacity: 0,
duration: 0.8
}, 0)
.from(next.container.querySelector('.layer-front'), {
y: 150,
opacity: 0,
duration: 0.8
}, 0);
await tl.play();
}
}]
});SaaS Dashboard with Minimal Transition
Fast, subtle transitions for app-like feel:
barba.init({
transitions: [{
name: 'dashboard',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.2,
ease: 'power1.inOut'
});
},
async enter({ next }) {
gsap.set(next.container, { opacity: 0 });
await gsap.to(next.container, {
opacity: 1,
duration: 0.2,
ease: 'power1.inOut'
});
}
}]
});---
Complete Starter Template
Full implementation with multiple transitions:
import barba from '@barba/core';
import gsap from 'gsap';
barba.init({
transitions: [
// Initial page load
{
name: 'initial-load',
once: async ({ next }) => {
const loader = document.querySelector('.page-loader');
await gsap.to(loader, {
opacity: 0,
duration: 0.5,
delay: 0.5
});
gsap.set(loader, { display: 'none' });
await gsap.from(next.container, {
opacity: 0,
y: 50,
duration: 0.8,
ease: 'power2.out'
});
}
},
// Same namespace: fast transition
{
name: 'same-namespace',
custom: ({ current, next }) => current.namespace === next.namespace,
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.3
});
},
async enter({ next }) {
await gsap.from(next.container, {
opacity: 0,
duration: 0.3
});
}
},
// Default: elegant fade
{
name: 'default',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
y: -30,
duration: 0.5,
ease: 'power2.in'
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
{ opacity: 0, y: 30 },
{ opacity: 1, y: 0, duration: 0.6, ease: 'power2.out' }
);
}
}
]
});
// Global hooks
barba.hooks.beforeEnter(() => {
window.scrollTo(0, 0);
});
barba.hooks.after(({ next }) => {
// Analytics
if (typeof gtag !== 'undefined') {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: next.url.path
});
}
// Re-initialize scripts
if (typeof Prism !== 'undefined') {
Prism.highlightAll();
}
});#!/usr/bin/env python3
"""
Barba.js Project Setup Script
Initializes a new Barba.js project with boilerplate HTML, CSS, and JavaScript files.
Usage:
./project_setup.py # Interactive mode
./project_setup.py --name my-project # Create project in ./my-project
./project_setup.py --name my-project --transition fade
./project_setup.py --name my-project --minimal # Minimal setup without examples
Options:
--name NAME Project directory name (required in CLI mode)
--transition TYPE Transition type (fade, slide, scale, stagger, curtain)
--minimal Create minimal setup without example pages
--no-install Skip npm install step
--output-dir DIR Parent directory for project (default: current directory)
"""
import os
import sys
import argparse
import subprocess
from pathlib import Path
def create_package_json(project_name: str) -> str:
"""Generate package.json content."""
return f'''{{
"name": "{project_name}",
"version": "1.0.0",
"description": "Barba.js page transition project",
"main": "src/main.js",
"scripts": {{
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}},
"keywords": ["barba", "page-transitions", "gsap"],
"author": "",
"license": "MIT",
"devDependencies": {{
"@barba/core": "^2.9.7",
"gsap": "^3.12.5",
"vite": "^5.0.0"
}}
}}
'''
def create_index_html(minimal: bool = False) -> str:
"""Generate index.html content."""
return '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home - Barba.js Project</title>
<meta name="description" content="Home page with Barba.js transitions">
<link rel="stylesheet" href="/src/style.css">
</head>
<body data-barba="wrapper">
<header class="site-header">
<nav>
<a href="/" class="logo">Barba.js</a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About</a></li>
<li><a href="/contact.html">Contact</a></li>
</ul>
</nav>
</header>
<main data-barba="container" data-barba-namespace="home">
<div class="hero">
<h1 class="stagger-item">Welcome to Barba.js</h1>
<p class="stagger-item">Smooth page transitions without full page reloads</p>
<a href="/about.html" class="btn stagger-item">Learn More</a>
</div>
<section class="features">
<div class="feature stagger-item">
<h2>Fast</h2>
<p>No page reloads, instant transitions</p>
</div>
<div class="feature stagger-item">
<h2>Smooth</h2>
<p>Beautiful GSAP-powered animations</p>
</div>
<div class="feature stagger-item">
<h2>Easy</h2>
<p>Simple API, powerful results</p>
</div>
</section>
</main>
<footer class="site-footer">
<p>© 2025 Barba.js Project</p>
</footer>
<div class="page-loader">Loading...</div>
<div class="transition-curtain"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
'''
def create_about_html() -> str:
"""Generate about.html content."""
return '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About - Barba.js Project</title>
<meta name="description" content="About page with Barba.js transitions">
<link rel="stylesheet" href="/src/style.css">
</head>
<body data-barba="wrapper">
<header class="site-header">
<nav>
<a href="/" class="logo">Barba.js</a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About</a></li>
<li><a href="/contact.html">Contact</a></li>
</ul>
</nav>
</header>
<main data-barba="container" data-barba-namespace="about">
<div class="content-page">
<h1 class="stagger-item">About Barba.js</h1>
<p class="stagger-item">Barba.js is a small (7kb minified and compressed) library that helps you create fluid and smooth transitions between your website's pages.</p>
<p class="stagger-item">It helps reduce the delay between your pages, minimize browser HTTP requests and enhance your user's web experience.</p>
<a href="/contact.html" class="btn stagger-item">Get in Touch</a>
</div>
</main>
<footer class="site-footer">
<p>© 2025 Barba.js Project</p>
</footer>
<div class="page-loader">Loading...</div>
<div class="transition-curtain"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
'''
def create_contact_html() -> str:
"""Generate contact.html content."""
return '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact - Barba.js Project</title>
<meta name="description" content="Contact page with Barba.js transitions">
<link rel="stylesheet" href="/src/style.css">
</head>
<body data-barba="wrapper">
<header class="site-header">
<nav>
<a href="/" class="logo">Barba.js</a>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About</a></li>
<li><a href="/contact.html">Contact</a></li>
</ul>
</nav>
</header>
<main data-barba="container" data-barba-namespace="contact">
<div class="content-page">
<h1 class="stagger-item">Contact Us</h1>
<p class="stagger-item">Get in touch to learn more about Barba.js.</p>
<form class="contact-form stagger-item">
<input type="text" placeholder="Name" required>
<input type="email" placeholder="Email" required>
<textarea placeholder="Message" rows="5" required></textarea>
<button type="submit" class="btn">Send Message</button>
</form>
</div>
</main>
<footer class="site-footer">
<p>© 2025 Barba.js Project</p>
</footer>
<div class="page-loader">Loading...</div>
<div class="transition-curtain"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
'''
def create_css() -> str:
"""Generate style.css content."""
return '''* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #333;
}
/* Header */
.site-header {
background: #667eea;
color: white;
padding: 1rem 2rem;
position: sticky;
top: 0;
z-index: 100;
}
.site-header nav {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.site-header .logo {
font-size: 1.5rem;
font-weight: bold;
color: white;
text-decoration: none;
}
.site-header ul {
display: flex;
list-style: none;
gap: 2rem;
}
.site-header a {
color: white;
text-decoration: none;
transition: opacity 0.3s;
}
.site-header a:hover {
opacity: 0.8;
}
/* Container */
[data-barba="container"] {
min-height: calc(100vh - 60px - 80px);
}
/* Hero */
.hero {
padding: 4rem 2rem;
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.hero p {
font-size: 1.25rem;
margin-bottom: 2rem;
}
/* Features */
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
padding: 4rem 2rem;
max-width: 1200px;
margin: 0 auto;
}
.feature {
padding: 2rem;
background: #f5f5f5;
border-radius: 8px;
text-align: center;
}
.feature h2 {
margin-bottom: 1rem;
color: #667eea;
}
/* Content Page */
.content-page {
max-width: 800px;
margin: 0 auto;
padding: 4rem 2rem;
}
.content-page h1 {
margin-bottom: 2rem;
color: #667eea;
}
.content-page p {
margin-bottom: 1.5rem;
font-size: 1.1rem;
}
/* Contact Form */
.contact-form {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 2rem;
}
.contact-form input,
.contact-form textarea {
padding: 0.75rem;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 1rem;
font-family: inherit;
}
.contact-form input:focus,
.contact-form textarea:focus {
outline: none;
border-color: #667eea;
}
/* Button */
.btn {
display: inline-block;
padding: 0.75rem 2rem;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 4px;
border: none;
font-size: 1rem;
cursor: pointer;
transition: background 0.3s;
}
.btn:hover {
background: #5568d3;
}
/* Footer */
.site-footer {
background: #333;
color: white;
text-align: center;
padding: 2rem;
}
/* Page Loader */
.page-loader {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.5rem;
color: #667eea;
opacity: 0;
z-index: 9998;
pointer-events: none;
}
/* Transition Curtain */
.transition-curtain {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
z-index: 9999;
pointer-events: none;
transform: translateY(-100%);
}
/* Responsive */
@media (max-width: 768px) {
.hero h1 {
font-size: 2rem;
}
.hero p {
font-size: 1rem;
}
.site-header ul {
gap: 1rem;
}
}
'''
def create_main_js(transition_type: str = 'fade') -> str:
"""Generate main.js content with specified transition."""
transitions_code = {
'fade': ''' {
name: 'fade',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
duration: 0.5,
ease: 'power2.inOut'
});
},
async enter({ next }) {
gsap.set(next.container, { opacity: 0 });
await gsap.to(next.container, {
opacity: 1,
duration: 0.5,
ease: 'power2.inOut'
});
}
}''',
'slide': ''' {
name: 'slide',
sync: true,
leave({ current }) {
return gsap.to(current.container, {
x: '-100%',
duration: 0.7,
ease: 'power3.inOut'
});
},
enter({ next }) {
gsap.set(next.container, { x: '100%' });
return gsap.to(next.container, {
x: '0%',
duration: 0.7,
ease: 'power3.inOut'
});
}
}''',
'scale': ''' {
name: 'scale-fade',
async leave({ current }) {
await gsap.to(current.container, {
opacity: 0,
scale: 0.95,
duration: 0.5,
ease: 'power2.in'
});
},
async enter({ next }) {
await gsap.fromTo(next.container,
{ opacity: 0, scale: 1.05 },
{ opacity: 1, scale: 1, duration: 0.6, ease: 'power2.out' }
);
}
}''',
'stagger': ''' {
name: 'stagger',
async leave({ current }) {
const tl = gsap.timeline();
tl.to(current.container.querySelectorAll('.stagger-item'), {
y: -50,
opacity: 0,
duration: 0.5,
stagger: 0.05,
ease: 'power2.in'
})
.to(current.container, {
opacity: 0,
duration: 0.3
}, '-=0.2');
await tl.play();
},
async enter({ next }) {
const tl = gsap.timeline();
gsap.set(next.container.querySelectorAll('.stagger-item'), {
y: 50,
opacity: 0
});
tl.to(next.container.querySelectorAll('.stagger-item'), {
y: 0,
opacity: 1,
duration: 0.6,
stagger: 0.05,
ease: 'power2.out'
});
await tl.play();
}
}''',
'curtain': ''' {
name: 'curtain',
async leave({ current }) {
const curtain = document.querySelector('.transition-curtain');
await gsap.fromTo(curtain,
{ yPercent: -100 },
{ yPercent: 0, duration: 0.6, ease: 'power2.inOut' }
);
},
async enter({ next }) {
const curtain = document.querySelector('.transition-curtain');
await gsap.to(curtain, {
yPercent: 100,
duration: 0.6,
ease: 'power2.inOut'
});
}
}'''
}
transition_code = transitions_code.get(transition_type, transitions_code['fade'])
return f'''import barba from '@barba/core';
import gsap from 'gsap';
// Initialize Barba.js
barba.init({{
transitions: [
{transition_code}
]
}});
// Global hooks
barba.hooks.beforeEnter(() => {{
// Reset scroll position
window.scrollTo(0, 0);
}});
barba.hooks.before(() => {{
// Show loader
gsap.to('.page-loader', {{ opacity: 1, duration: 0.3 }});
}});
barba.hooks.after(() => {{
// Hide loader
gsap.to('.page-loader', {{ opacity: 0, duration: 0.3 }});
}});
// Contact form handler (demo)
document.addEventListener('submit', (e) => {{
if (e.target.classList.contains('contact-form')) {{
e.preventDefault();
alert('Form submission would happen here!');
}}
}});
'''
def create_vite_config() -> str:
"""Generate vite.config.js content."""
return '''import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
input: {
main: 'index.html',
about: 'about.html',
contact: 'contact.html'
}
}
}
});
'''
def create_readme(project_name: str) -> str:
"""Generate README.md content."""
return f'''# {project_name}
Barba.js page transition project with GSAP animations.
## Setup
Install dependencies:
```bash
npm install
```
## Development
Start development server:
```bash
npm run dev
```
Open http://localhost:5173 in your browser.
## Build
Build for production:
```bash
npm run build
```
Output will be in `dist/` directory.
## Preview Production Build
```bash
npm run preview
```
## Project Structure
```
{project_name}/
├── index.html # Home page
├── about.html # About page
├── contact.html # Contact page
├── src/
│ ├── main.js # Barba.js initialization
│ └── style.css # Global styles
├── package.json
├── vite.config.js
└── README.md
```
## Customization
### Change Transition
Edit `src/main.js` and modify the transition in `barba.init()`.
See available transition types in the Barba.js documentation.
### Add More Pages
1. Create new HTML file (e.g., `services.html`)
2. Add `data-barba="container"` to main content
3. Add `data-barba-namespace="services"` to identify the page
4. Add link to navigation
5. Update `vite.config.js` to include the new page in build
## Resources
- [Barba.js Documentation](https://barba.js.org)
- [GSAP Documentation](https://greensock.com/docs/)
- [Vite Documentation](https://vitejs.dev)
'''
def create_project(
name: str,
output_dir: str = '.',
transition: str = 'fade',
minimal: bool = False,
no_install: bool = False
) -> None:
"""Create Barba.js project structure."""
project_path = Path(output_dir) / name
# Check if directory exists
if project_path.exists():
print(f"❌ Error: Directory '{project_path}' already exists")
sys.exit(1)
print(f"🎬 Creating Barba.js project: {name}")
print(f"📁 Location: {project_path}")
print()
# Create directories
project_path.mkdir(parents=True)
(project_path / 'src').mkdir()
print("✅ Created project directory")
# Create files
files = {
'package.json': create_package_json(name),
'index.html': create_index_html(minimal),
'src/style.css': create_css(),
'src/main.js': create_main_js(transition),
'vite.config.js': create_vite_config(),
'README.md': create_readme(name)
}
if not minimal:
files['about.html'] = create_about_html()
files['contact.html'] = create_contact_html()
for file_path, content in files.items():
full_path = project_path / file_path
full_path.write_text(content)
print(f"✅ Created {file_path}")
print()
print("=" * 50)
print("✅ Project created successfully!")
print()
print("Next steps:")
print(f" 1. cd {name}")
if not no_install:
print(" 2. Running npm install...")
print()
try:
subprocess.run(['npm', 'install'], cwd=project_path, check=True)
print()
print("✅ Dependencies installed!")
print()
print("To start development:")
print(f" cd {name}")
print(" npm run dev")
except subprocess.CalledProcessError:
print("❌ npm install failed. Run manually:")
print(f" cd {name}")
print(" npm install")
print(" npm run dev")
except FileNotFoundError:
print("⚠️ npm not found. Install dependencies manually:")
print(f" cd {name}")
print(" npm install")
print(" npm run dev")
else:
print(" 2. npm install")
print(" 3. npm run dev")
print()
def interactive_mode():
"""Run setup in interactive mode with prompts."""
print("🎬 Barba.js Project Setup")
print("=" * 50)
print()
# Project name
name = input("Project name: ").strip()
if not name:
print("❌ Project name is required")
sys.exit(1)
# Transition type
print()
print("Available transition types:")
print(" 1. fade (simple fade)")
print(" 2. slide (horizontal slide)")
print(" 3. scale (zoom with fade)")
print(" 4. stagger (staggered elements)")
print(" 5. curtain (curtain overlay)")
print()
transition_choice = input("Select transition (1-5, default: 1): ").strip()
transitions = ['fade', 'slide', 'scale', 'stagger', 'curtain']
try:
transition_idx = int(transition_choice) - 1 if transition_choice else 0
transition = transitions[transition_idx]
except (ValueError, IndexError):
transition = 'fade'
# Minimal setup
print()
minimal_input = input("Minimal setup? (no example pages) [y/N]: ").strip().lower()
minimal = minimal_input in ['y', 'yes']
# Install dependencies
print()
no_install_input = input("Skip npm install? [y/N]: ").strip().lower()
no_install = no_install_input in ['y', 'yes']
print()
print("=" * 50)
print()
# Create project
create_project(name, '.', transition, minimal, no_install)
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description='Initialize a new Barba.js project',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--name',
help='Project directory name'
)
parser.add_argument(
'--transition',
choices=['fade', 'slide', 'scale', 'stagger', 'curtain'],
default='fade',
help='Transition type (default: fade)'
)
parser.add_argument(
'--minimal',
action='store_true',
help='Create minimal setup without example pages'
)
parser.add_argument(
'--no-install',
action='store_true',
help='Skip npm install step'
)
parser.add_argument(
'--output-dir',
default='.',
help='Parent directory for project (default: current directory)'
)
args = parser.parse_args()
# Interactive mode if no name specified
if not args.name:
interactive_mode()
return
# CLI mode
create_project(
args.name,
args.output_dir,
args.transition,
args.minimal,
args.no_install
)
if __name__ == '__main__':
main()
Related skills
How it compares
Use Barba.js scaffolding when you need HTML multi-page transitions; use framework starters when the app is a React or Next.js SPA.
FAQ
What does barba-js do?
Page transitions library for creating fluid, smooth transitions between website pages. Use this skill when implementing page transitions, creating SPA-like experiences, adding animated route changes, or building websites
When should I use barba-js?
Page transitions library for creating fluid, smooth transitions between website pages. Use this skill when implementing page transitions, creating SPA-like experiences, adding animated route changes, or building websites
What are common prerequisites?
--- name: barba-js description: Page transitions library for creating fluid, smooth transitions between website pages.
Is Barba Js safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.