Function Registry

Your JavaScript doesn't automatically re-run when Barba swaps in a new page. That's the problem the function registry solves. In this lesson we'll cover the three init functions, and the pattern you'll use over and over again to register your own code.
Lesson Notes
Our function registry
When Barba swaps in a new page, your JavaScript doesn't automatically re-run. The browser already executed your scripts on initial load. The function registry gives you clear moments to reinitialize your code.
function initOnceFunctions() {
// Runs once on first page load
}
function initBeforeEnterFunctions(next) {
container = next || document;
// Runs before the enter animation
}
function initAfterEnterFunctions(next) {
container = next || document;
// Runs after enter animation completes
}When to use which
-
initOnceFunctionsFirst page load only. Use it for global stuff outside the container (nav, audio player) -
initBeforeEnterFunctionsBefore enter animation. Use for things that need to be setup before page is visible. -
initAfterEnterFunctionsAfter transition completes. Most things: sliders, scroll triggers, accordions, video players
Registering your functions
The has() check looks for the element in the current container. If it exists, the function runs. If not, it skips. This makes scaling your project pretty effortless.
function initAfterEnterFunctions(next) {
container = next || document;
if (has('[data-accordion]')) initAccordion();
if (has('[data-something]')) initSomething();
// Etc...
lenis.resize();
ScrollTrigger.refresh();
}Writing your functions
function initAccordion() {
const accordions = container.querySelectorAll('[data-accordion]');
accordions.forEach(accordion => {
// your accordion logic
});
}Query from container to target only the new page content. Use document if you need to query elements outside the Barba container (like modals).
containerscoped to the new page, use for elements inside the Barba container.documentwhole page, use for persistent elements like nav or modals.
Boilerplate
// -----------------------------------------
// OSMO PAGE TRANSITION BOILERPLATE
// -----------------------------------------
gsap.registerPlugin(CustomEase);
history.scrollRestoration = "manual";
let lenis = null;
let nextPage = document;
let onceFunctionsInitialized = false;
const hasLenis = typeof window.Lenis !== "undefined";
const hasScrollTrigger = typeof window.ScrollTrigger !== "undefined";
const rmMQ = window.matchMedia("(prefers-reduced-motion: reduce)");
let reducedMotion = rmMQ.matches;
rmMQ.addEventListener?.("change", e => (reducedMotion = e.matches));
rmMQ.addListener?.(e => (reducedMotion = e.matches));
const has = (s) => !!nextPage.querySelector(s);
let staggerDefault = 0.05;
let durationDefault = 0.6;
CustomEase.create("osmo", "0.625, 0.05, 0, 1");
gsap.defaults({ ease: "osmo", duration: durationDefault });
// -----------------------------------------
// FUNCTION REGISTRY
// -----------------------------------------
function initOnceFunctions() {
initLenis();
if (onceFunctionsInitialized) return;
onceFunctionsInitialized = true;
// Runs once on first load
// if (has('[data-something]')) initSomething();
}
function initBeforeEnterFunctions(next) {
nextPage = next || document;
// Runs before the enter animation
// if (has('[data-something]')) initSomething();
}
function initAfterEnterFunctions(next) {
nextPage = next || document;
// Runs after enter animation completes
// if (has('[data-something]')) initSomething();
if(hasLenis){
lenis.resize();
}
if (hasScrollTrigger) {
ScrollTrigger.refresh();
}
}
// -----------------------------------------
// PAGE TRANSITIONS
// -----------------------------------------
function runPageOnceAnimation(next) {
const tl = gsap.timeline();
tl.call(() => {
resetPage(next)
}, null, 0);
return tl;
}
function runPageLeaveAnimation(current, next) {
const tl = gsap.timeline({
onComplete: () => { current.remove() }
});
if (reducedMotion) {
// Immediate swap behavior if user prefers reduced motion
return tl.set(current, { autoAlpha: 0 });
}
tl.to(current, { autoAlpha: 0, duration: 0.4 });
return tl;
}
function runPageEnterAnimation(next){
const tl = gsap.timeline();
if (reducedMotion) {
// Immediate swap behavior if user prefers reduced motion
tl.set(next, { autoAlpha: 1 });
tl.add("pageReady")
tl.call(resetPage, [next], "pageReady");
return new Promise(resolve => tl.call(resolve, null, "pageReady"));
}
tl.add("startEnter", 0.6);
tl.fromTo(next, {
autoAlpha: 0,
},{
autoAlpha: 1,
}, "startEnter");
tl.add("pageReady");
tl.call(resetPage, [next], "pageReady");
return new Promise(resolve => {
tl.call(resolve, null, "pageReady");
});
}
// -----------------------------------------
// BARBA HOOKS + INIT
// -----------------------------------------
barba.hooks.beforeEnter(data => {
// Position new container on top
gsap.set(data.next.container, {
position: "fixed",
top: 0,
left: 0,
right: 0,
});
if(hasLenis){
lenis.stop();
}
initBeforeEnterFunctions(data.next.container);
applyThemeFrom(data.next.container);
});
barba.hooks.afterLeave(() => {
if(hasScrollTrigger){
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
}
});
barba.hooks.enter(data => {
initBarbaNavUpdate(data);
})
barba.hooks.afterEnter(data => {
// Run page functions
initAfterEnterFunctions(data.next.container);
// Settle
if(hasLenis){
lenis.resize();
lenis.start();
}
if(hasScrollTrigger){
ScrollTrigger.refresh();
}
});
barba.init({
debug: true, // Set to 'false' in production
timeout: 7000,
preventRunning: true,
transitions: [
{
name: "default",
sync: true,
// First load
async once(data) {
initOnceFunctions();
return runPageOnceAnimation(data.next.container);
},
// Current page leaves
async leave(data) {
return runPageLeaveAnimation(data.current.container, data.next.container);
},
// New page enters
async enter(data) {
return runPageEnterAnimation(data.next.container);
}
}
],
});
// -----------------------------------------
// GENERIC + HELPERS
// -----------------------------------------
const themeConfig = {
light: {
nav: "dark",
transition: "light"
},
dark: {
nav: "light",
transition: "dark"
}
};
function applyThemeFrom(container) {
const pageTheme = container?.dataset?.pageTheme || "light";
const config = themeConfig[pageTheme] || themeConfig.light;
document.body.dataset.pageTheme = pageTheme;
const transitionEl = document.querySelector('[data-theme-transition]');
if (transitionEl) {
transitionEl.dataset.themeTransition = config.transition;
}
const nav = document.querySelector('[data-theme-nav]');
if (nav) {
nav.dataset.themeNav = config.nav;
}
}
function initLenis() {
if (lenis) return; // already created
if (!hasLenis) return;
lenis = new Lenis({
lerp: 0.165,
wheelMultiplier: 1.25,
});
if (hasScrollTrigger) {
lenis.on("scroll", ScrollTrigger.update);
}
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
}
function resetPage(container){
window.scrollTo(0, 0);
gsap.set(container, { clearProps: "position,top,left,right" });
if(hasLenis){
lenis.resize();
lenis.start();
}
}
function debounceOnWidthChange(fn, ms) {
let last = innerWidth,
timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
if (innerWidth !== last) {
last = innerWidth;
fn.apply(this, args);
}
}, ms);
};
}
function initBarbaNavUpdate(data) {
var tpl = document.createElement('template');
tpl.innerHTML = data.next.html.trim();
var nextNodes = tpl.content.querySelectorAll('[data-barba-update]');
var currentNodes = document.querySelectorAll('nav [data-barba-update]');
currentNodes.forEach(function (curr, index) {
var next = nextNodes[index];
if (!next) return;
// Aria-current sync
var newStatus = next.getAttribute('aria-current');
if (newStatus !== null) {
curr.setAttribute('aria-current', newStatus);
} else {
curr.removeAttribute('aria-current');
}
// Class list sync
var newClassList = next.getAttribute('class') || '';
curr.setAttribute('class', newClassList);
});
}
// -----------------------------------------
// YOUR FUNCTIONS GO BELOW HERE
// -----------------------------------------




















































































































































