smooth plugin
Drag that eases in and out. The slider follows your pointer with a lerp, keeps its momentum when you let go, and settles on the nearest slide. Horizontal trackpad scroll gets the same treatment.
Usage
import { Slider } from '@andresclua/sliderkit'
import { smooth, arrows } from '@andresclua/sliderkit-plugins'
new Slider('#el', {
items: 3,
loop: true,
touch: false, // smooth() replaces the core drag
mouseDrag: false,
plugins: [smooth(), arrows()],
}) Presets
Start from a named behaviour and override whatever you like. Every other option wins over the preset.
smooth({ preset: 'snappy' })
smooth({ preset: 'floaty', inertia: 1200, skew: true }) | Preset | Feel | Values |
|---|---|---|
'smooth' (default) | Soft lag, generous glide, always settles on a slide. | lerp 0.1 · inertia 350 · snap |
'snappy' | Quick and controlled. A fling never travels more than one slide. | lerp 0.22 · inertia 120 · snap · maxFling 1 |
'floaty' | Heavy and dreamy. A lot of lag and a long glide. | lerp 0.05 · inertia 700 · snap |
'free' | No snapping. It stops wherever the momentum runs out. | lerp 0.08 · inertia 900 · no snap · maxFling 8 |
The presets are exported as a plain object, so you can read them, spread them or build your own: smooth({ ...smoothPresets.floaty, lerp: 0.04 }).
Options
| Option | Type | Default | Description |
|---|---|---|---|
preset | 'smooth' | 'snappy' | 'floaty' | 'free' | 'smooth' | Starting point. See above. |
lerp | number | preset | Fraction of the remaining distance covered per 60 fps frame. Lower is floatier, 1 is no smoothing. Frame-rate independent. |
inertia | number | preset | Milliseconds of release velocity projected forward. 0 disables the fling. |
snap | boolean | preset | Settle on a slide after a drag or wheel gesture. |
maxFling | number | 2 × visible items | Most slides a single fling can travel. |
dragSpeed | number | 1 | Multiplier for pointer and wheel movement. |
wheel | boolean | true | Horizontal wheel / trackpad scroll. Vertical scroll is left alone so the page still scrolls. |
wheelSpeed | number | 1 | Extra multiplier for the wheel only, on top of dragSpeed. |
snapDelay | number | 140 | Milliseconds of wheel silence before it settles on a slide. |
dragThreshold | number | 5 | Pixels of movement after which a drag stops counting as a click, so it doesn't open the link under the pointer. |
skew | boolean | object | false | Lean the slides into the movement. true uses the defaults below. |
onUpdate | (plugin) => void | — | Called every frame the slider moves, and once more when it comes to rest. Receives the plugin, so you can read velocity, position, progress and positions. See below. |
raf | boolean | true | Run its own animation loop. Set false to call plugin.tick(now) from your own requestAnimationFrame, so it moves in the same frame as other things you draw (a canvas, for example). |
Skew
Slides lean in the direction they move, in proportion to the speed, and ease back to straight when they stop.
smooth({
skew: {
target: '.card', // selector inside each slide (default: the slide itself)
perSecond: 2.5, // degrees per slide/second of velocity
max: 14, // max degrees either way
ease: 0.2, // how fast it returns to 0 (0–1 per frame)
},
}) It sets transform: skewX() on the target, so use an element that has no transform of its own. For any other effect, read velocity yourself (next section).
onUpdate and live state
onUpdate is the extension point: the plugin gives you the numbers, you decide what to do with them. The skew option is just a ready-made use of it.
const slider = new Slider('#el', {
touch: false, mouseDrag: false,
plugins: [
smooth({
onUpdate(sm) {
// runs every frame the slider moves, and once more when it stops (velocity 0)
const scale = 1 - Math.min(Math.abs(sm.velocity) * 0.02, 0.1)
cards.forEach(card => card.style.transform = `scale(${scale})`)
},
}),
],
}) | Member | Type | Description |
|---|---|---|
velocity | number | Signed speed in slides per second, eased so it ramps up and decays smoothly (it never jumps when you let go). Positive towards the next slide, negative towards the previous one, 0 at rest. |
position | number | Where the slider is, in slides from the first one. Wraps around when looping. |
progress | number | 0–1 along the slider. |
positions | readonly number[] | One value per real slide. 0 = at the start of the viewport, 1 = one slide further right, negative = before it. With loop: true it wraps inside [-1, n-1), so every slide that can be on screen has a continuous value. Loop clones show the same slide, so they share its value. |
dragging | boolean | true while a pointer is holding the slider. |
tick(now) | (number) => void | Advances the animation by one frame. Only needed with raf: false. |
Tip: build effects from continuous functions of the values (distance to a point, multiplied by a factor). Avoid Math.round, % or anything that steps: when the slider settles it crosses those thresholds and the effect jumps.
The same properties are readable at any time from the plugin you created (const sm = smooth()), for example from your own requestAnimationFrame. positions is reused between frames: copy it if you keep it.
Drawing on a canvas
If something else renders every frame (a WebGL canvas, for instance), create the plugin with smooth({ raf: false }) and call sm.tick(now) at the start of your own requestAnimationFrame callback, before you read the slides' positions. That way the slider and the canvas always update in the same frame and never drift apart. The home page does exactly this.
How it works
- The plugin keeps a continuous position (in slides) and a target. Every frame the position moves a fraction of the way to the target, and the result is written as the track's transform.
- Dragging moves the target. On release, the pointer velocity of the last 100 ms is projected forward (
inertia) and the target snaps to a slide. - The core stays in charge of the index: it is told where you landed, so arrows, pagination,
a11yand events keep working. Clicking an arrow animates there by the shortest way round. - With
loop: truethe position wraps over the clones the core already creates, so there is no jump at the seam.
Notes
- Horizontal axis only.
- Pass
touch: falseandmouseDrag: false, otherwise both drag systems fight. - The plugin disables the core's CSS transition on the track (it would lag behind the per-frame movement).
speedno longer affects the slide movement. - With
loop: falsethe movement stops hard at the ends (no rubber-band). - A drag never ends in a click on a link or button under the pointer.
Demos
- Smooth drag demos — the plugin on its own, a skew boilerplate you can copy, your own effect with
onUpdate, and the four presets side by side. - Home page — the same slider with a WebGL canvas that follows the slides.