Text reveal via beforeSlideChange / afterSlideChange
Each slide contains a real background image plus a text block with an eyebrow label, title, and description. beforeSlideChange animates the current text out (staggered, upward). afterSlideChange animates the incoming text in from below. GSAP handles all timing — the slider core only fires the events.
View code
<div class="hero-slider" id="my-slider">
<div class="c--slider-a__wrapper">
<div class="c--slider-a__slide hs-slide" data-slide
style="background-image:url('https://picsum.photos/seed/hs1/900/420')">
<div class="hs-content">
<span class="hs-tag">Architecture</span>
<h2 class="hs-title">Built for the modern web</h2>
<p class="hs-desc">Lightweight, accessible, and dependency-free at its core.</p>
</div>
</div>
<div class="c--slider-a__slide hs-slide" data-slide
style="background-image:url('https://picsum.photos/seed/hs2/900/420')">
<div class="hs-content">
<span class="hs-tag">Performance</span>
<h2 class="hs-title">Zero layout thrash</h2>
<p class="hs-desc">Transforms only — no reflow on every frame.</p>
</div>
</div>
<div class="c--slider-a__slide hs-slide" data-slide
style="background-image:url('https://picsum.photos/seed/hs3/900/420')">
<div class="hs-content">
<span class="hs-tag">Plugins</span>
<h2 class="hs-title">Extend without the weight</h2>
<p class="hs-desc">Import only the plugins you need — tree-shaking does the rest.</p>
</div>
</div>
<div class="c--slider-a__slide hs-slide" data-slide
style="background-image:url('https://picsum.photos/seed/hs4/900/420')">
<div class="hs-content">
<span class="hs-tag">WebGL</span>
<h2 class="hs-title">GPU-powered transitions</h2>
<p class="hs-desc">GLSL shaders wired directly to the slide-change lifecycle.</p>
</div>
</div>
</div>
</div>
<div id="my-pag" class="c--slider-a__pagination"></div> .hero-slider {
position: relative;
width: 100%;
overflow: hidden;
border-radius: 8px;
}
.c--slider-a__wrapper { display: flex; will-change: transform; }
.hs-slide {
flex-shrink: 0;
width: 100%;
height: 380px;
background-size: cover;
background-position: center;
position: relative;
}
/* Dark gradient so text is readable */
.hs-slide::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.1) 60%);
border-radius: inherit;
}
.hs-content {
position: absolute;
bottom: 40px;
left: 40px;
right: 40px;
z-index: 2;
color: #fff;
}
.hs-tag {
display: inline-block;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
background: rgba(108, 43, 217, 0.85);
padding: 3px 10px;
border-radius: 99px;
margin-bottom: 12px;
}
.hs-title {
font-size: clamp(1.4rem, 3vw, 2rem);
font-weight: 700;
line-height: 1.2;
margin-bottom: 8px;
}
.hs-desc {
font-size: 0.95rem;
opacity: 0.85;
max-width: 480px;
}
/* Pagination dots */
.c--slider-a__pagination { display: flex; justify-content: center; padding: 12px 0; gap: 6px; }
.c--slider-a__pagination-bullet { width: 10px; height: 10px; border-radius: 50%; background: #d1d5db; border: none; cursor: pointer; transition: background 0.2s; }
.c--slider-a__pagination-bullet--active { background: #6C2BD9; }
/* Arrows */
.c--slider-a__arrow {
position: absolute; top: 50%; transform: translateY(-50%);
z-index: 10; width: 40px; height: 40px; border-radius: 50%;
border: none; background: rgba(255,255,255,0.15); backdrop-filter: blur(4px);
cursor: pointer; color: #fff;
}
.c--slider-a__arrow--prev { left: 16px; }
.c--slider-a__arrow--next { right: 16px; } .hero-slider {
position: relative;
width: 100%;
overflow: hidden;
border-radius: 8px;
}
.c--slider-a__wrapper { display: flex; will-change: transform; }
.hs-slide {
flex-shrink: 0;
width: 100%;
height: 380px;
background-size: cover;
background-position: center;
position: relative;
}
/* Dark gradient so text is readable */
.hs-slide::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.1) 60%);
border-radius: inherit;
}
.hs-content {
position: absolute;
bottom: 40px;
left: 40px;
right: 40px;
z-index: 2;
color: #fff;
}
.hs-tag {
display: inline-block;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
background: rgba(108, 43, 217, 0.85);
padding: 3px 10px;
border-radius: 99px;
margin-bottom: 12px;
}
.hs-title {
font-size: clamp(1.4rem, 3vw, 2rem);
font-weight: 700;
line-height: 1.2;
margin-bottom: 8px;
}
.hs-desc {
font-size: 0.95rem;
opacity: 0.85;
max-width: 480px;
}
/* Pagination dots */
.c--slider-a__pagination { display: flex; justify-content: center; padding: 12px 0; gap: 6px; }
.c--slider-a__pagination-bullet { width: 10px; height: 10px; border-radius: 50%; background: #d1d5db; border: none; cursor: pointer; transition: background 0.2s; }
.c--slider-a__pagination-bullet--active { background: #6C2BD9; }
/* Arrows */
.c--slider-a__arrow {
position: absolute; top: 50%; transform: translateY(-50%);
z-index: 10; width: 40px; height: 40px; border-radius: 50%;
border: none; background: rgba(255,255,255,0.15); backdrop-filter: blur(4px);
cursor: pointer; color: #fff;
}
.c--slider-a__arrow--prev { left: 16px; }
.c--slider-a__arrow--next { right: 16px; } import { Slider } from '@andresclua/sliderkit'
import { arrows, pagination } from '@andresclua/sliderkit-plugins'
import { gsap } from 'gsap'
const slider = new Slider('#my-slider', {
loop: true,
speed: 600,
plugins: [
arrows(),
pagination({ el: '#my-pag', type: 'dots', clickable: true }),
],
})
// Collect text containers from real slides only (not clones)
const contents = [...slider.slides].map(s => s.querySelector('.hs-content'))
// Animate initial slide text in on load
gsap.fromTo(contents[0]?.children ?? [],
{ y: 30, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.6, stagger: 0.1, ease: 'power3.out', delay: 0.2 }
)
// Out: before the slide starts moving
slider.on('beforeSlideChange', ({ index }) => {
const el = contents[index]
if (!el) return
gsap.to(el.children, {
y: -20, opacity: 0, duration: 0.22, stagger: 0.05, ease: 'power2.in',
})
})
// In: after the new slide is in position
slider.on('afterSlideChange', ({ index }) => {
const el = contents[index]
if (!el) return
gsap.fromTo(el.children,
{ y: 36, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.55, stagger: 0.09, ease: 'power3.out' }
)
}) Staggered card entrance
A multi-slide layout where each card's inner elements animate in individually on afterSlideChange. The stagger creates a ripple feel — icon, title, description, and badge each appear 80ms apart. Touch or click an arrow to see the wave re-trigger.
View code
<div class="card-slider" id="my-cards">
<div class="c--slider-a__wrapper">
<div class="c--slider-a__slide card-slide" data-slide>
<div class="card-body">
<div class="card-icon">⚡</div>
<h3 class="card-title">Instant setup</h3>
<p class="card-text">One import, one constructor call. No config required.</p>
<span class="card-badge">Core</span>
</div>
</div>
<div class="c--slider-a__slide card-slide" data-slide>
<div class="card-body">
<div class="card-icon">🎯</div>
<h3 class="card-title">Events API</h3>
<p class="card-text">Fine-grained lifecycle hooks for any custom integration.</p>
<span class="card-badge">Lifecycle</span>
</div>
</div>
<div class="c--slider-a__slide card-slide" data-slide>
<div class="card-body">
<div class="card-icon">🧩</div>
<h3 class="card-title">Plugin system</h3>
<p class="card-text">Drop-in plugins — tree-shaken, typed, and composable.</p>
<span class="card-badge">Plugins</span>
</div>
</div>
<div class="c--slider-a__slide card-slide" data-slide>
<div class="card-body">
<div class="card-icon">🌊</div>
<h3 class="card-title">WebGL effects</h3>
<p class="card-text">GLSL shaders that speak the same event language.</p>
<span class="card-badge">WebGL</span>
</div>
</div>
<div class="c--slider-a__slide card-slide" data-slide>
<div class="card-body">
<div class="card-icon">♿</div>
<h3 class="card-title">Accessible</h3>
<p class="card-text">ARIA roles, keyboard nav, and reduced-motion support built in.</p>
<span class="card-badge">A11y</span>
</div>
</div>
</div>
</div>
<div id="my-cards-pag" class="c--slider-a__pagination"></div> .card-slider {
position: relative;
width: 100%;
overflow: hidden;
border-radius: 8px;
background: #f3f4f6;
}
.c--slider-a__wrapper { display: flex; will-change: transform; }
.card-slide {
flex-shrink: 0;
padding: 8px;
}
.card-body {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 28px 24px 24px;
height: 220px;
display: flex;
flex-direction: column;
gap: 8px;
}
.card-icon { font-size: 2rem; line-height: 1; }
.card-title { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.card-text { font-size: 0.875rem; color: #6b7280; line-height: 1.5; flex: 1; margin: 0; }
.card-badge {
display: inline-block;
align-self: flex-start;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
background: #ede9fe;
color: #6C2BD9;
padding: 2px 10px;
border-radius: 99px;
}
.c--slider-a__pagination { display: flex; justify-content: center; padding: 10px 0; gap: 6px; }
.c--slider-a__pagination-bullet { width: 8px; height: 8px; border-radius: 50%; background: #d1d5db; border: none; cursor: pointer; }
.c--slider-a__pagination-bullet--active { background: #6C2BD9; }
.c--slider-a__arrow {
position: absolute; top: 50%; transform: translateY(-50%);
z-index: 10; width: 36px; height: 36px; border-radius: 50%;
border: none; background: rgba(255,255,255,0.95);
cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
.c--slider-a__arrow--prev { left: 4px; }
.c--slider-a__arrow--next { right: 4px; } .card-slider {
position: relative;
width: 100%;
overflow: hidden;
border-radius: 8px;
background: #f3f4f6;
}
.c--slider-a__wrapper { display: flex; will-change: transform; }
.card-slide {
flex-shrink: 0;
padding: 8px;
}
.card-body {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 28px 24px 24px;
height: 220px;
display: flex;
flex-direction: column;
gap: 8px;
}
.card-icon { font-size: 2rem; line-height: 1; }
.card-title { font-size: 1.1rem; font-weight: 700; color: #111827; margin: 0; }
.card-text { font-size: 0.875rem; color: #6b7280; line-height: 1.5; flex: 1; margin: 0; }
.card-badge {
display: inline-block;
align-self: flex-start;
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
background: #ede9fe;
color: #6C2BD9;
padding: 2px 10px;
border-radius: 99px;
}
.c--slider-a__pagination { display: flex; justify-content: center; padding: 10px 0; gap: 6px; }
.c--slider-a__pagination-bullet { width: 8px; height: 8px; border-radius: 50%; background: #d1d5db; border: none; cursor: pointer; }
.c--slider-a__pagination-bullet--active { background: #6C2BD9; }
.c--slider-a__arrow {
position: absolute; top: 50%; transform: translateY(-50%);
z-index: 10; width: 36px; height: 36px; border-radius: 50%;
border: none; background: rgba(255,255,255,0.95);
cursor: pointer; box-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
.c--slider-a__arrow--prev { left: 4px; }
.c--slider-a__arrow--next { right: 4px; } import { Slider } from '@andresclua/sliderkit'
import { arrows, pagination } from '@andresclua/sliderkit-plugins'
import { gsap } from 'gsap'
const slider = new Slider('#my-cards', {
slidesPerPage: 3,
gutter: 0,
loop: true,
speed: 400,
plugins: [
arrows(),
pagination({ el: '#my-cards-pag', type: 'dots', clickable: true }),
],
})
const cards = [...slider.slides].map(s => s.querySelector('.card-body'))
const n = cards.length
function animateIn(index) {
const card = cards[index % n]
if (!card) return
gsap.fromTo(card.children,
{ y: 24, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.45, stagger: 0.08, ease: 'power3.out' }
)
}
// Animate the initial visible cards
const spp = slider.getInfo().slidesPerPage
for (let i = 0; i < spp; i++) animateIn(i)
// Track direction so we know which side the new card enters from
let dir = 'next'
slider.on('beforeSlideChange', ({ direction }) => { dir = direction })
slider.on('afterSlideChange', ({ index, previousIndex }) => {
const spp = slider.getInfo().slidesPerPage
const entering = []
if (dir === 'next') {
// New card(s) sliding in from the right
for (let i = Math.max(index, previousIndex + spp); i < index + spp; i++) {
entering.push(i % n)
}
} else {
// New card(s) sliding in from the left
for (let i = index; i <= Math.min(index + spp - 1, previousIndex - 1); i++) {
entering.push(i % n)
}
}
// Loop boundary or large jump: animate all visible cards
if (entering.length === 0) {
for (let i = 0; i < spp; i++) entering.push((index + i) % n)
}
entering.forEach(animateIn)
})