Skip to main content

Basic Demos

Overview demo: horizontal carousel with CSS-unit gap, smooth interpolation, and per-slide progress.

Key props: gap: "1rem", lerp: 0.1, duration: 750, origin: "center", loop: true.

Also reads track accessors in the update callback — current, target, min, max, influence, and each slide's progress.

HTML
<div class="fullpage-center">
<div class="info">
<div>Current: <span data-value-current></span></div>
<div>Target: <span data-value-target></span></div>
<div>Min: <span data-value-min></span></div>
<div>Max: <span data-value-max></span></div>
<div>Loop: <span data-value-loop></span></div>
<div>Impulse: <span data-value-impulse></span></div>
<div>Interpolating: <span data-value-interpolating></span></div>
<div>Idle: <span data-value-idle></span></div>
</div>

<div class="carousel" id="carousel">
<div class="slide">
<div class="slide-wrap" data-snap-parallax-x="-50%" data-snap-parallax-x-scope="none" data-snap-parallax-x-impulse="3">
<div>Slide 1</div>
<div data-progress>0%</div>
</div>
</div>

<div class="slide">
<div class="slide-wrap" data-snap-parallax-x="-50%" data-snap-parallax-x-scope="none" data-snap-parallax-x-impulse="3">
<div>Slide 2</div>
<div data-progress>0%</div>
</div>
</div>

<div class="slide">
<div class="slide-wrap" data-snap-parallax-x="-50%" data-snap-parallax-x-scope="none" data-snap-parallax-x-impulse="3">
<div>Slide 3</div>
<div data-progress>0%</div>
</div>
</div>

<div class="slide">
<div class="slide-wrap" data-snap-parallax-x="-50%" data-snap-parallax-x-scope="none" data-snap-parallax-x-impulse="3">
<div>Slide 4</div>
<div data-progress>0%</div>
</div>
</div>

<div class="slide">
<div class="slide-wrap" data-snap-parallax-x="-50%" data-snap-parallax-x-scope="none" data-snap-parallax-x-impulse="3">
<div>Slide 5</div>
<div data-progress>0%</div>
</div>
</div>

<div class="slide">
<div class="slide-wrap" data-snap-parallax-x="-50%" data-snap-parallax-x-scope="none" data-snap-parallax-x-impulse="3">
<div>Slide 6</div>
<div data-progress>0%</div>
</div>
</div>

<div class="slide">
<div class="slide-wrap" data-snap-parallax-x="-50%" data-snap-parallax-x-scope="none" data-snap-parallax-x-impulse="3">
<div>Slide 7</div>
<div data-progress>0%</div>
</div>
</div>
</div>
</div>
CSS
.info {
display: grid;
grid-template-columns: 1fr;
width: 80%;
gap: 0.5rem;

font-size: 0.875rem;

@media (min-width: 769px) {
grid-template-columns: 1fr 1fr;

>* {
&:nth-child(2n + 2) {
text-align: right;
}
}
}
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
width: var(--size);
height: 100%;

display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.slide-wrap {
width: 100%;
height: 100%;

display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;

gap: 1rem;
padding: 1rem;
border-radius: 1rem;

background: rgba(255, 255, 255, 0.08);
}

.slide [data-progress] {
width: 100%;
padding-top: 1rem;
border-top: 1px solid currentColor;
}
JavaScript
import {
Snap,
EaseOutBack
} from "vevet";

const container = document.getElementById("carousel");

const valueCurrent = document.querySelector("[data-value-current]");
const valueTarget = document.querySelector("[data-value-target]");
const valueMin = document.querySelector("[data-value-min]");
const valueMax = document.querySelector("[data-value-max]");
const valueLoop = document.querySelector("[data-value-loop]");
const valueImpulse = document.querySelector("[data-value-impulse]");
const valueInterpolating = document.querySelector("[data-value-interpolating]");
const valueIdle = document.querySelector("[data-value-idle]");

const carousel = new Snap({
container,
gap: "1rem",
lerp: 0.1,
duration: 750,
easing: EaseOutBack,
grabCursor: true,
freemode: "sticky",
origin: "center",
loop: true
});

container.classList.add("ready");

carousel.slides.forEach(({
element
}) => {
element.progressElement = element.querySelector("[data-progress]");
});

carousel.on("update", () => {
valueCurrent.innerHTML = carousel.current.toFixed(2);
valueTarget.innerHTML = carousel.target.toFixed(2);
valueMin.innerHTML = carousel.min.toFixed(2);
valueMax.innerHTML = carousel.max.toFixed(2);
valueLoop.innerHTML = carousel.loopCount;
valueImpulse.innerHTML = carousel.impulse.toFixed(2);
valueInterpolating.innerHTML = carousel.isInterpolating;
valueIdle.innerHTML = carousel.isIdle;

carousel.slides.forEach(({
element,
coord,
progress,
index
}) => {
element.style.transform = `translate(${coord}px, 0)`;

const percent = (progress * 100).toFixed(0);

element.progressElement.innerHTML = `${percent}%`;
});
});

carousel.on("idle", () => {
valueIdle.innerHTML = carousel.isIdle;
});

Default active index

Start on a specific slide with activeIndex: 1 — a zero-based slide index.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;

height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
width: var(--size);
height: 100%;

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
activeIndex: 1,
gap: "1.25rem",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Variable size

Slides can have different widths or heights. Some slides may be wider than the container and scroll with inertia.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;

height: 50vw;
max-height: 50vh;

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
width: var(--size);
height: 100%;

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}

.slide:nth-child(1) {
width: calc(1.15 * var(--size));
}

.slide:nth-child(3) {
width: calc(3 * var(--size));
min-width: 130vw;
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Vertical

Vertical carousel with direction: "vertical".

HTML
<div class="wrapper">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.wrapper {
display: grid;
padding: 1rem;
height: 100svh;
overflow: hidden;
}

.carousel {
position: relative;
width: 100%;
height: 100%;

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1rem",
direction: "vertical",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(0, ${coord}px)`;
});
});

Origin

Align snap magnets with origin: "center". Equivalent to the deprecated centered: true.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: 20,
origin: "center",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Origin end

Align snap magnets with origin: "end" — the trailing edge of each slide sticks to the trailing edge of the container.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
origin: "end",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Loop

Infinite carousel with loop: true.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: 20,
origin: "center",
loop: true,
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Gap

Spacing between slides via gap. Accepts CSS units such as px, rem, vw, vh, and svh.

Example: gap: "10vw".

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "10vw",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Grab cursor

Show grab/grabbing cursor during interaction with grabCursor: true.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Wheel

Horizontal carousel controlled by the mouse wheel. Enable with wheel: true and wheelAxis: "y". Slides snap to magnets when wheeling stops.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
wheel: true,
wheelAxis: "y",
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Free wheel

Magnetic snapping while swiping, but free scrolling on wheel release via stickOnWheelEnd: false.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
wheel: true,
wheelAxis: "y",
stickOnWheelEnd: false,
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Throttled wheel

With followWheel: false, wheel input is throttled automatically so one gesture does not skip multiple slides.

HTML
<div class="wrapper">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.wrapper {
display: grid;
padding: 1rem;
height: 100svh;
overflow: hidden;
}

.carousel {
position: relative;
width: 100%;
height: 100%;

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1rem",
direction: "vertical",
wheel: true,
followWheel: false,
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(0, ${coord}px)`;
});
});

Throttled wheel by time

Time-based wheel throttling with followWheel: false and wheelThrottle: 100 — wheel input is blocked for 100ms after each slide change, including during transitions.

HTML
<div class="wrapper">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.wrapper {
display: grid;
padding: 1rem;
height: 100svh;
overflow: hidden;
}

.carousel {
position: relative;
width: 100%;
height: 100%;

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1rem ",
direction: "vertical",
wheel: true,
followWheel: false,
wheelThrottle: 100,
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(0, ${coord}px)`;
});
});

Hypersmooth

Very smooth motion with low lerp and long duration. Example: lerp: 0.05.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
wheel: true,
wheelAxis: "y",
lerp: 0.05,
duration: 1500,
grabCursor: true,
freemode: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Freemode

Free scrolling with inertia via freemode: true. When loop: false, movement bounces at the track edges.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide" style="width: 150vw">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
freemode: true,
wheel: true,
wheelAxis: "y"
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Sticky freemode

Combines freemode inertia with magnetic snapping at the end of the gesture: freemode: "sticky".

Movement stays within bounds and always settles on the nearest slide magnet when inertia ends.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide" style="width: 150vw">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
freemode: "sticky",
wheel: true,
wheelAxis: "y",
shortSwipes: false
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Rewind

Wrap-around navigation without looping the track: rewind: true. Works when loop: false — from the last slide, next goes to the first, and vice versa.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
rewind: true,
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Friction

Add resistance during wheel and swipe interactions with friction: 0.3 and swipeFriction: true. Pulls movement toward slide magnets.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
wheel: true,
grabCursor: true,
wheelAxis: "y",
lerp: 0.2,
friction: 0.3,
swipeFriction: true,
stickOnWheelEnd: false
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Edge friction

Extra resistance near the carousel edges. Enabled by default — override with edgeFriction: 0.25.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
edgeFriction: 0.25,
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Interval

Auto-advance slides with interval: 500 — moves to the next slide 500ms after each transition ends.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: '1.25rem',
origin: "center",
loop: true,
grabCursor: true,
interval: 500
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Easing

Custom transition easing. Example: easing: EaseOutBack, duration: 850.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap,
EaseOutBack
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
easing: EaseOutBack,
duration: 850
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Swipe no follow

Disable live swipe tracking so slides change only after the gesture ends: followSwipe: false.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
followSwipe: false,
grabCursor: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Reverse swipe

Reverse swipe direction with a negative swipeSpeed. Example: swipeSpeed: -1.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap,
EaseOutBack
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
swipeSpeed: -1
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Manual RTL setup:

  • dir="rtl" on the container
  • swipeSpeed: -1
  • apply transforms with the opposite sign
HTML
<div class="fullpage-center">
<div class="carousel" id="carousel" dir="rtl">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
right: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap,
EaseOutBack
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
swipeSpeed: -1
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord
}) => {
element.style.transform = `translate(${-coord}px, 0)`;
});
});

Slides to scroll

Scroll multiple slides per interaction with slidesToScroll: 2.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: calc(50% - 0.625rem);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
slidesToScroll: 2
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

Adaptive Snap

Use Responsive to change Snap props at breakpoints:

new Responsive(carousel, [
{
at: '@media (max-width: 768px)',
props: {
slidesToScroll: 1,
followSwipe: false,
},
},
]);
HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: calc(50% - 0.625rem);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);

@media (max-width: 768px) {
width: 100%;
}
}
JavaScript
import {
Snap,
Responsive
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
slidesToScroll: 2
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});

new Responsive(carousel, [{
at: "@media (max-width: 768px)",
props: {
slidesToScroll: 1,
followSwipe: false
}
}]);

Progress

Read each slide's progress and index in the update callback — useful for parallax, opacity, and 3D effects.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
<div class="slide">6</div>
<div class="slide">7</div>
</div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
--size: clamp(max(10rem, 25vw), 50vh, 50vw);

position: relative;
width: 100%;
height: var(--size);

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--size);

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap,
EaseOutBack
} from "vevet";

const container = document.getElementById("carousel");

const carousel = new Snap({
container,
gap: "1.25rem",
grabCursor: true,
origin: "center",
loop: true
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
progress,
index
}) => {
element.style.transform = `translate(${coord}px, 0)`;

element.innerHTML = `${index} / ${progress.toFixed(2)}`;
});
});

Virtual

Virtual slides: pass SnapSlide instances with virtual: true. DOM nodes are created on init; only visible slides stay in the document.

HTML
<div class="fullpage-center">
<div class="carousel" id="carousel"></div>
</div>
CSS
.fullpage-center {
padding-inline: 1.25rem;
}

.carousel {
position: relative;
width: 100%;

height: 50vw;
max-height: 50vh;

opacity: 0;
transition: opacity 0.25s linear;
}

.carousel.ready {
opacity: 1;
}

.slide {
position: absolute;
top: 0;
left: 0;
height: 100%;

display: flex;
flex-direction: row;
align-items: center;
justify-content: center;

border-radius: 1rem;
background: rgba(255, 255, 255, 0.08);
}
JavaScript
import {
Snap,
SnapSlide
} from "vevet";

const container = document.getElementById("carousel");

const createSlides = count => {
const slides = new Array(count).fill(0).map((item, index) => {
const size = "40vw";

const element = document.createElement("div");
element.className = "slide";
element.innerHTML = `<span>${index}</span>`;
element.style.width = size;

return new SnapSlide(element, {
size,
virtual: true
});

});

return slides;
};

const carousel = new Snap({
container,
slides: createSlides(500),
gap: "1.25rem",
grabCursor: true,
loop: true,
origin: "center"
});

container.classList.add("ready");

carousel.on("update", () => {
carousel.slides.forEach(({
element,
coord,
isVisible
}) => {
element.style.transform = `translate(${coord}px, 0)`;
});
});