Gutter & Edge Padding

3 slides per page with 16px gutters. Edge padding peeks the prev/next slide.

A
B
C
D
E
F
G
View code
<div class="c--slider-a" id="my-slider">
  <div class="c--slider-a__wrapper">
    <div class="c--slider-a__slide">A</div>
    <div class="c--slider-a__slide">B</div>
    <div class="c--slider-a__slide">C</div>
    <div class="c--slider-a__slide">D</div>
    <div class="c--slider-a__slide">E</div>
    <div class="c--slider-a__slide">F</div>
    <div class="c--slider-a__slide">G</div>
  </div>
</div>
<div id="my-pagination" class="c--slider-a__pagination"></div>
.c--slider-a {
  position: relative;
  width: 100%;
  overflow: hidden;
}
.c--slider-a__wrapper {
  display: flex;
  will-change: transform;
}
.c--slider-a__slide {
  flex-shrink: 0;
  /* width is calculated by the slider based on slidesPerPage + gutter */
  height: 260px;
}

/* Fraction pagination */
.c--slider-a__pagination {
  display: flex;
  justify-content: center;
  padding: 12px 0;
}
.c--slider-a__pagination-fraction {
  font-size: 0.85rem;
  color: #6b7280;
  font-variant-numeric: tabular-nums;
}

/* Auto-created 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.9);
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}
.c--slider-a__arrow--prev { left: 12px; }
.c--slider-a__arrow--next { right: 12px; }
.c--slider-a__arrow--disabled { opacity: 0.3; pointer-events: none; }
.c--slider-a {
  position: relative;
  width: 100%;
  overflow: hidden;

  &__wrapper {
    display: flex;
    will-change: transform;
  }

  &__slide {
    flex-shrink: 0;
    height: 260px;
  }

  &__pagination {
    display: flex;
    justify-content: center;
    padding: 12px 0;
  }

  &__pagination-fraction {
    font-size: 0.85rem;
    color: #6b7280;
    font-variant-numeric: tabular-nums;
  }

  &__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.9);
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);

    &--prev {
      left: 12px;
    }

    &--next {
      right: 12px;
    }

    &--disabled {
      opacity: 0.3;
      pointer-events: none;
    }
  }
}
import { Slider } from '@andresclua/sliderkit'
import { arrows, pagination } from '@andresclua/sliderkit-plugins'

new Slider('#my-slider', {
  slidesPerPage: 3,
  gutter: 16,
  edgePadding: 40,
  loop: false,
  rewind: true,
  plugins: [
    arrows(),
    pagination({ el: '#my-pagination', type: 'fraction' }),
  ],
})

Center focus — active slide scales up (GSAP)

edgePadding makes adjacent slides peek in from the sides. On every slide change GSAP animates the active slide to scale(1) and all others to scale(0.85), drawing the eye to the centre item. The outer wrapper clips the overflow so the sides look intentionally cropped.

View code
<div class="center-wrap">
  <div class="c--slider-a" id="my-slider">
    <div class="c--slider-a__wrapper">
      <div class="c--slider-a__slide slide-img" style="background-image:url('https://picsum.photos/seed/cf1/800/400')"></div>
      <div class="c--slider-a__slide slide-img" style="background-image:url('https://picsum.photos/seed/cf2/800/400')"></div>
      <div class="c--slider-a__slide slide-img" style="background-image:url('https://picsum.photos/seed/cf3/800/400')"></div>
      <div class="c--slider-a__slide slide-img" style="background-image:url('https://picsum.photos/seed/cf4/800/400')"></div>
      <div class="c--slider-a__slide slide-img" style="background-image:url('https://picsum.photos/seed/cf5/800/400')"></div>
    </div>
  </div>
</div>
/* Outer wrapper clips the overflow */
.center-wrap {
  overflow: hidden;
  border-radius: 8px;
}
/* Slider itself overflows so scale is visible */
.center-wrap .c--slider-a {
  overflow: visible;
  background: transparent;
  border-radius: 0;
}
.slide-img {
  height: 280px;
  border-radius: 8px;
  background-size: cover;
  background-position: center;
  will-change: transform;
  transform-origin: center center;
}
.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.9);
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.c--slider-a__arrow--prev { left: 12px; }
.c--slider-a__arrow--next { right: 12px; }
/* Outer wrapper clips the overflow */
.center-wrap {
  overflow: hidden;
  border-radius: 8px;
}
/* Slider itself overflows so scale is visible */
.center-wrap .c--slider-a {
  overflow: visible;
  background: transparent;
  border-radius: 0;
}
.slide-img {
  height: 280px;
  border-radius: 8px;
  background-size: cover;
  background-position: center;
  will-change: transform;
  transform-origin: center center;
}
.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.9);
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.c--slider-a__arrow--prev { left: 12px; }
.c--slider-a__arrow--next { right: 12px; }
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { gsap } from 'gsap'

const slider = new Slider('#my-slider', {
  slidesPerPage: 1,
  edgePadding: 64,
  loop: true,
  speed: 480,
  plugins: [arrows()],
})

// Set initial state: all slides small, active at full size
const el = document.querySelector('#my-slider')

function syncScale() {
  const all    = el.querySelectorAll('.c--slider-a__slide')
  const active = el.querySelector('.c--slider-a__slide--active')
  gsap.to(all,    { scale: 0.85, duration: 0.4, ease: 'power2.out' })
  if (active)
    gsap.to(active, { scale: 1,    duration: 0.4, ease: 'power2.out', overwrite: 'auto' })
}

// Initialise scale without animation
gsap.set(el.querySelectorAll('.c--slider-a__slide'), { scale: 0.85 })
gsap.set(el.querySelector('.c--slider-a__slide--active'), { scale: 1 })

slider.on('afterSlideChange', syncScale)

Depth-of-field filmstrip (GSAP blur)

5 slides visible at once, infinite loop. The center slide is always sharp — adjacent slides blur progressively outward, mimicking a camera's depth of field. Blur is measured from each slide's visual center position so it works correctly on real slides and loop clones alike.

View code
<div class="film-wrap">
  <div class="c--slider-a" id="my-slider">
    <div class="c--slider-a__wrapper">
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof1/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof2/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof3/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof4/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof5/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof6/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof7/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof8/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof9/600/360')"></div>
      <div class="c--slider-a__slide film-slide" style="background-image:url('https://picsum.photos/seed/dof10/600/360')"></div>
    </div>
  </div>
</div>
.film-wrap {
  overflow: hidden;
  border-radius: 8px;
  background: #111;
}
.film-slide {
  height: 240px;
  border-radius: 4px;
  background-size: cover;
  background-position: center;
  will-change: filter;
}
.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.15);
  backdrop-filter: blur(4px);
  cursor: pointer;
}
.c--slider-a__arrow--prev { left: 4px; }
.c--slider-a__arrow--next { right: 4px; }
.film-wrap {
  overflow: hidden;
  border-radius: 8px;
  background: #111;
}
.film-slide {
  height: 240px;
  border-radius: 4px;
  background-size: cover;
  background-position: center;
  will-change: filter;
}
.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.15);
  backdrop-filter: blur(4px);
  cursor: pointer;
}
.c--slider-a__arrow--prev { left: 4px; }
.c--slider-a__arrow--next { right: 4px; }
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { gsap } from 'gsap'

// Blur by visual distance from container centre — works on real slides AND loop clones
// slot:  [0]   [1]   [2]    [3]   [4]
// dist:   2     1     0      1     2
// blur: 12px   5px   0px    5px  12px
const BLUR_BY_DIST = [0, 2, 4]

const slider = new Slider('#my-slider', {
  slidesPerPage: 5,
  gutter: 24,
  loop: true,
  speed: 500,
  plugins: [arrows()],
})

const container = document.querySelector('#my-slider')

function applyBlur() {
  const cRect      = container.getBoundingClientRect()
  const centerX    = cRect.left + cRect.width / 2
  const allSlides  = container.querySelectorAll('.c--slider-a__slide')
  const slideW     = allSlides[0]?.getBoundingClientRect().width ?? 1

  allSlides.forEach(slide => {
    const r     = slide.getBoundingClientRect()
    const sx    = r.left + r.width / 2
    const dist  = Math.round(Math.abs(sx - centerX) / (slideW + 6))
    const px    = BLUR_BY_DIST[Math.min(dist, BLUR_BY_DIST.length - 1)]
    gsap.to(slide, { filter: `blur(${px}px)`, duration: 0.45, ease: 'power2.out' })
  })
}

applyBlur()
slider.on('afterSlideChange', applyBlur)

Fan rotation — cards with GSAP rotate

5 cards visible at once. The center card sits flat (0°) and adjacent cards tilt progressively up to ±12°, creating a hand-of-cards fan effect. Each card has a cover image and a short label at the bottom left. GSAP animates the rotation on every afterSlideChange.

Mountain
Ocean
Forest
Desert
City
Coast
Valley
Canyon
Lake
Meadow
View code
<div class="fan-wrap">
  <div class="c--slider-a" id="my-slider">
    <div class="c--slider-a__wrapper">
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan1/600/400')">
        <span class="rot-label">Mountain</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan2/600/400')">
        <span class="rot-label">Ocean</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan3/600/400')">
        <span class="rot-label">Forest</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan4/600/400')">
        <span class="rot-label">Desert</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan5/600/400')">
        <span class="rot-label">City</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan6/600/400')">
        <span class="rot-label">Coast</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan7/600/400')">
        <span class="rot-label">Valley</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan8/600/400')">
        <span class="rot-label">Canyon</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan9/600/400')">
        <span class="rot-label">Lake</span>
      </div>
      <div class="c--slider-a__slide rot-card" style="background-image:url('https://picsum.photos/seed/fan10/600/400')">
        <span class="rot-label">Meadow</span>
      </div>
    </div>
  </div>
</div>
.fan-wrap {
  overflow: hidden;
  border-radius: 8px;
  background: #f3f4f6;
  padding: 32px 0;
}
.fan-wrap .c--slider-a {
  overflow: visible;
  background: transparent;
}
.rot-card {
  height: 220px;
  border-radius: 12px;
  background-size: cover;
  background-position: center;
  position: relative;
  transform-origin: center bottom;
  box-shadow: 0 4px 20px rgba(0,0,0,0.22);
  overflow: hidden;
  will-change: transform;
}
.rot-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0,0,0,0.55) 0%, transparent 55%);
}
.rot-label {
  position: absolute;
  bottom: 12px;
  left: 14px;
  color: #fff;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  z-index: 1;
}
.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.9);
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.c--slider-a__arrow--prev { left: 8px; }
.c--slider-a__arrow--next { right: 8px; }
.fan-wrap {
  overflow: hidden;
  border-radius: 8px;
  background: #f3f4f6;
  padding: 32px 0;
}
.fan-wrap .c--slider-a {
  overflow: visible;
  background: transparent;
}
.rot-card {
  height: 220px;
  border-radius: 12px;
  background-size: cover;
  background-position: center;
  position: relative;
  transform-origin: center bottom;
  box-shadow: 0 4px 20px rgba(0,0,0,0.22);
  overflow: hidden;
  will-change: transform;
}
.rot-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0,0,0,0.55) 0%, transparent 55%);
}
.rot-label {
  position: absolute;
  bottom: 12px;
  left: 14px;
  color: #fff;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  z-index: 1;
}
.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.9);
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.c--slider-a__arrow--prev { left: 8px; }
.c--slider-a__arrow--next { right: 8px; }
import { Slider } from '@andresclua/sliderkit'
import { arrows } from '@andresclua/sliderkit-plugins'
import { gsap } from 'gsap'

// Rotation by visual position: center slot = 0°, ±6° per step, capped ±12°
// Works on real slides AND loop clones via getBoundingClientRect

const slider = new Slider('#my-slider', {
  slidesPerPage: 5,
  gutter: 24,
  loop: true,
  speed: 500,
  plugins: [arrows()],
})

const container = document.querySelector('#my-slider')
const wrapper   = container.querySelector('.c--slider-a__wrapper')

function applyRotation(snap = false) {
  // offsetLeft is layout-based (unaffected by CSS rotation on slides).
  // Adding wrapper.style.transform's translateX gives position within the container.
  // Works at init AND on loop clones without any timing issues.
  const tx     = new DOMMatrix(wrapper.style.transform).m41
  const cx     = container.offsetWidth / 2
  const slideW = slider.slides[0].offsetWidth || 1

  container.querySelectorAll('.c--slider-a__slide').forEach(slide => {
    const center = slide.offsetLeft + tx + slide.offsetWidth / 2
    const slot   = Math.round((center - cx) / (slideW + 24))
    const deg    = Math.max(-12, Math.min(12, slot * 6))
    if (snap) gsap.set(slide, { rotation: deg })
    else      gsap.to(slide,  { rotation: deg, duration: 0.45, ease: 'power2.out' })
  })
}

applyRotation(true)
slider.on('afterSlideChange', () => applyRotation())