boostify.scroll() — lazy load slider + GSAP
boostify.scroll({ distance: 300 }) fires once the user has scrolled 300 px. Inside the async callback, @andresclua/sliderkit, @andresclua/sliderkit-plugins and gsap are all fetched in parallel — zero bundle cost before that point. Two animations: staggered y-fade for cards visible on arrival, x-slide from the correct side for cards that enter during navigation.
Scroll down to load
Nothing loaded yet
@andresclua/sliderkit, @andresclua/sliderkit-plugins and gsap are absent from the initial bundle.
Scroll 300 px and watch the Network tab.
↓
View code
<!-- A tall intro block forces the user to scroll before reaching the slider -->
<div class="lazy-intro">
<p class="lazy-intro__eyebrow">Scroll down to load</p>
<h2 class="lazy-intro__title">Nothing loaded yet</h2>
<p class="lazy-intro__sub">
@andresclua/sliderkit, @andresclua/sliderkit-plugins and gsap are all <strong>absent from the initial bundle</strong>.<br>
Scroll 300 px and watch the Network tab.
</p>
<div class="lazy-intro__arrow">↓</div>
</div>
<!-- Slider shell — no JS touches it until after scroll -->
<div class="lazy-slider-wrap">
<div class="c--slider-a" id="my-slider">
<div class="c--slider-a__wrapper">
<div class="c--slider-a__slide lazy-card" data-slide>
<div class="lazy-card__body">
<div class="lazy-card__icon">🚀</div>
<h3 class="lazy-card__title">Fast by default</h3>
<p class="lazy-card__text">Zero-cost abstractions. Ships only what you use.</p>
</div>
</div>
<div class="c--slider-a__slide lazy-card" data-slide>
<div class="lazy-card__body">
<div class="lazy-card__icon">🎯</div>
<h3 class="lazy-card__title">Event-driven</h3>
<p class="lazy-card__text">Fine-grained lifecycle hooks for any integration.</p>
</div>
</div>
<div class="c--slider-a__slide lazy-card" data-slide>
<div class="lazy-card__body">
<div class="lazy-card__icon">🧩</div>
<h3 class="lazy-card__title">Plugin system</h3>
<p class="lazy-card__text">Tree-shaken, typed, and fully composable.</p>
</div>
</div>
<div class="c--slider-a__slide lazy-card" data-slide>
<div class="lazy-card__body">
<div class="lazy-card__icon">♿</div>
<h3 class="lazy-card__title">Accessible</h3>
<p class="lazy-card__text">ARIA roles, keyboard nav, reduced-motion built in.</p>
</div>
</div>
<div class="c--slider-a__slide lazy-card" data-slide>
<div class="lazy-card__body">
<div class="lazy-card__icon">🌊</div>
<h3 class="lazy-card__title">WebGL effects</h3>
<p class="lazy-card__text">GLSL shaders speaking the same event language.</p>
</div>
</div>
<div class="c--slider-a__slide lazy-card" data-slide>
<div class="lazy-card__body">
<div class="lazy-card__icon">📐</div>
<h3 class="lazy-card__title">Responsive</h3>
<p class="lazy-card__text">Breakpoints via ResizeObserver — no window resize hacks.</p>
</div>
</div>
</div>
</div>
</div> .lazy-intro {
text-align: center;
padding: 60px 24px 80px;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 48px;
}
.lazy-intro__eyebrow {
font-size: 0.7rem; font-weight: 700;
letter-spacing: 0.12em; text-transform: uppercase;
color: #6C2BD9; margin-bottom: 12px;
}
.lazy-intro__title {
font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: #111827;
}
.lazy-intro__sub {
font-size: 0.95rem; color: #6b7280; line-height: 1.6; max-width: 480px; margin: 0 auto 28px;
}
.lazy-intro__arrow {
font-size: 2rem; animation: bounce 1.2s ease-in-out infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(10px); }
}
.lazy-slider-wrap {
position: relative;
background: #f9fafb;
border-radius: 12px;
padding: 32px 0;
}
.c--slider-a {
position: relative;
overflow: hidden;
}
.c--slider-a__wrapper { display: flex; will-change: transform; }
.lazy-card { flex-shrink: 0; padding: 8px; }
.lazy-card__body {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 28px 20px 22px;
height: 190px;
display: flex;
flex-direction: column;
gap: 8px;
}
.lazy-card__icon { font-size: 1.8rem; line-height: 1; }
.lazy-card__title { font-size: 1rem; font-weight: 700; color: #111827; margin: 0; }
.lazy-card__text { font-size: 0.84rem; color: #6b7280; line-height: 1.5; flex: 1; margin: 0; }
.c--slider-a__arrow {
position: absolute; top: 50%; transform: translateY(-50%);
z-index: 10; width: 36px; height: 36px; border-radius: 50%;
border: none; background: #fff; cursor: pointer;
box-shadow: 0 1px 6px rgba(0,0,0,0.14);
}
.c--slider-a__arrow--prev { left: 8px; }
.c--slider-a__arrow--next { right: 8px; } @keyframes bounce {
0%, 100% { transform: translateY(0);
}
50% {
transform: translateY(10px);
}
.lazy-slider-wrap {
position: relative;
background: #f9fafb;
border-radius: 12px;
padding: 32px 0;
}
.lazy-intro {
text-align: center;
padding: 60px 24px 80px;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 48px;
&__eyebrow {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #6C2BD9;
margin-bottom: 12px;
}
&__title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 12px;
color: #111827;
}
&__sub {
font-size: 0.95rem;
color: #6b7280;
line-height: 1.6;
max-width: 480px;
margin: 0 auto 28px;
}
&__arrow {
font-size: 2rem;
animation: bounce 1.2s ease-in-out infinite;
}
}
.c--slider-a {
position: relative;
overflow: hidden;
&__wrapper {
display: flex;
will-change: transform;
}
&__arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: #fff;
cursor: pointer;
box-shadow: 0 1px 6px rgba(0,0,0,0.14);
&--prev {
left: 8px;
}
&--next {
right: 8px;
}
}
}
.lazy-card {
flex-shrink: 0;
padding: 8px;
&__body {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 28px 20px 22px;
height: 190px;
display: flex;
flex-direction: column;
gap: 8px;
}
&__icon {
font-size: 1.8rem;
line-height: 1;
}
&__title {
font-size: 1rem;
font-weight: 700;
color: #111827;
margin: 0;
}
&__text {
font-size: 0.84rem;
color: #6b7280;
line-height: 1.5;
flex: 1;
margin: 0;
}
} import Boostify from 'boostify'
const boostify = new Boostify()
boostify.scroll({
distance: 300,
callback: async () => {
// All three modules fetched in parallel — zero cost before scroll
const [{ Slider }, { arrows }, { gsap }] = await Promise.all([
import('@andresclua/sliderkit'),
import('@andresclua/sliderkit-plugins'),
import('gsap'),
])
const slider = new Slider('#my-slider', {
slidesPerPage: 3,
gutter: 0,
loop: true,
speed: 420,
plugins: [arrows()],
})
const cards = [...slider.slides].map(s => s.querySelector('.lazy-card__body'))
const n = cards.length
let dir = 'next'
slider.on('beforeSlideChange', ({ direction }) => { dir = direction })
// ── Entrance animation — cards visible on arrival ──────────────────────
const spp = slider.getInfo().slidesPerPage
for (let i = 0; i < spp; i++) {
gsap.fromTo(cards[i]?.children ?? [],
{ y: 40, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.55, stagger: 0.09,
ease: 'power3.out', delay: i * 0.08 }
)
}
// ── Navigation animation — new card slides in from the side ───────────
slider.on('afterSlideChange', ({ index, previousIndex }) => {
const spp = slider.getInfo().slidesPerPage
const entering = []
if (dir === 'next') {
for (let i = Math.max(index, previousIndex + spp); i < index + spp; i++)
entering.push(i % n)
} else {
for (let i = index; i <= Math.min(index + spp - 1, previousIndex - 1); i++)
entering.push(i % n)
}
if (!entering.length)
for (let i = 0; i < spp; i++) entering.push((index + i) % n)
const fromX = dir === 'next' ? 36 : -36
entering.forEach(i => {
const card = cards[i]
if (!card) return
gsap.fromTo(card.children,
{ x: fromX, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.38, stagger: 0.07, ease: 'power2.out' }
)
})
})
},
})